generative modellierung - Sven Havemanns Webseite

Werbung
G ENERATIVE M ODELLIERUNG
Diplomarbeit, vorgelegt von Sven Havemann
Rheinische Friedrich-Wilhelms-Universität Bonn
Institut für Informatik III
30. November 1997
Versicherung
Hiermit versichere ich an Eides Statt, daß ich die vorliegende Arbeit „Generative Modellierung“
selbständig verfaßt und keine anderen als die angegebenen Quellen und Hilfsmittel verwendet
habe.
Bonn, den 7. Dezember 1997
Sven Havemann
3
Danksagung
An dieser Stelle möchte ich ganz herzlich all jenen danken, ohne die diese Arbeit nicht in dieser
Form möglich gewesen wäre.
An erster Stelle ist Herr Professor Dr. D. W. Fellner zu nennen, der mir die Freiheit gegeben hat,
eine faszinierende Idee zu verfolgen.
Für diese Idee danke ich – unbekannterweise – Herrn John M. Snyder.
Der Grafikgruppe, meinen heutigen und den ehemaligen Mitstreitern, gilt mein Dank für die lange
gute Zusammenarbeit, die vielen anregenden Diskussionen und die freundschaftliche Atmosphäre
innerhalb der Gruppe. – Ihnen verdanke ich das Fundament, das ich benutzen durfte.
Widmen möchte ich diese Arbeit meinen Eltern, denen ich ich für das Verständnis danke, das sie
mir stets entgegengebracht haben, und die besonders während meines Studiums immer hinter mir
standen.
5
Inhaltsverzeichnis
1 Einleitung
1
2 Evolution der Geometrischen Modellierung
2.1 Die Produktion einer Animation . . . . . . . . . .
2.2 Konzepte der Modellierung . . . . . . . . . . . . .
2.3 Präzisierung des Oberflächenbegriffs . . . . . . . .
2.4 Konventionelle Modellieransätze . . . . . . . . . .
2.4.1 Approximationsbasiertes Modellieren . . .
2.4.2 Objektorientiertes Modellieren . . . . . . .
2.4.3 Parametrisches Modellieren . . . . . . . .
2.5 Generatives Modellieren . . . . . . . . . . . . . .
2.5.1 Funktionale Konzepte in der Modellierung
2.5.2 Paradigmenwechsel . . . . . . . . . . . . .
2.6 Zusammenfassung . . . . . . . . . . . . . . . . .
3 Grundlagen
3.1 GENMOD-Programmiersprache nach Snyder
3.2 MRT . . . . . . . . . . . . . . . . . . . . . .
3.3 Open Inventor . . . . . . . . . . . . . . . . .
3.4 GeomView . . . . . . . . . . . . . . . . . .
3.5 TBag . . . . . . . . . . . . . . . . . . . . .
3.6 ActiveVRML . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4 Generative Modellierung
4.1 Generative Modellierung in C++ . . . . . . . . . . . .
4.1.1 Abbildungen als Objekte . . . . . . . . . . . .
4.1.2 Vergleich des Genmod-Paketes mit GENMOD
4.2 Generative Modellierung in Haskell . . . . . . . . . .
4.2.1 Warum Haskell? . . . . . . . . . . . . . . . .
4.2.2 Formulierung von Genmod in Haskell . . . . .
4.3 Zusammenfassung . . . . . . . . . . . . . . . . . . .
5 Implementationen von Genmod in C++ und Haskell
5.1 Implementation in C++ . . . . . . . . . . . . . . . . .
5.1.1 Der Genmod-Kern . . . . . . . . . . . . . . .
5.1.2 Die Kurven- und Flächenbibliothek . . . . . .
5.1.3 Integration von Kurven und Flächen in Genmod
5.2 Implementation in Haskell . . . . . . . . . . . . . . .
i
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
10
13
16
17
19
21
23
24
25
26
.
.
.
.
.
.
27
29
33
37
38
40
41
.
.
.
.
.
.
.
43
44
44
50
54
55
58
62
.
.
.
.
.
65
67
67
69
73
75
INHALTSVERZEICHNIS
ii
5.2.1
5.2.2
Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Instanzen der Basisklassen . . . . . . . . . . . . . . . . . . . . . . . . .
75
77
6 Beispiele und Erfahrungsbericht
6.1 Genmod-Beispiele . . . . . . . . . . . . . . . . . .
6.1.1 Sinusfläche mit Spirale . . . . . . . . . . . .
6.1.2 Morphing . . . . . . . . . . . . . . . . . . .
6.1.3 Sweep-Werkzeug . . . . . . . . . . . . . . .
6.1.4 Bézier-Blending . . . . . . . . . . . . . . .
6.1.5 Bézier-Blending mit Kugelnormale . . . . .
6.1.6 Bézier-Blending mit Kugeltangente . . . . .
6.1.7 Stuhl . . . . . . . . . . . . . . . . . . . . .
6.2 Haskell-Beispiele . . . . . . . . . . . . . . . . . . .
6.2.1 Frenet-Rahmen als Kurven . . . . . . . . . .
6.2.2 Parametrisierte Transformation . . . . . . . .
6.2.3 Funktion als Fläche und patch trimming . . .
6.2.4 Offset einer Fläche in Richtung der Normalen
6.2.5 Parametrisierte Konstruktion: Zaun . . . . .
6.2.6 Parametrisierte Konstruktion: Wendeltreppe .
6.2.7 rail product und wire product . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
79
79
80
81
82
83
84
85
86
87
87
88
89
90
91
92
94
7 Zusammenfassung
7.1 Modellierung ist funktional objektorientiert
7.2 Modellieren mit Abbildungen ist effizient .
7.3 Mögliche Realisierungen . . . . . . . . . .
7.4 Ausblick . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
95
95
96
97
99
Literaturverzeichnis
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
102
A Haskell-Code
105
A.1 Die Datei Genmod.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
A.2 Die Datei Vectors.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Kapitel 1
Einleitung
In dieser Arbeit soll ein neuer Ansatz zur praktischen Geometrischen Modellierung beschrieben
werden, die Generative Modellierung.
Die Konstruktion virtueller Räume ist ein aufwendiges Geschäft. Die Vielfalt der Formen und
Objekte unserer dreidimensionalen Welt scheint schier unerschöpflich, es verlangt ein hohes Maß
an Kreativität, all diese Formen zu erfassen, ihren Aufbau und ihre innere Logik zu entschlüsseln,
um sie schließlich nachkonstruieren und in einer eigenen, selbst definierten, simulierten Welt aufleben lassen zu können. Doch dies lohnt den Aufwand, denn die Gegenstände in einer solchen
virtuellen Welt sind manipulierbar, man kann ihre Form in schier unmöglicher Weise verändern,
eigene physikalische Gesetze wirken lassen, und so kann man Dinge zeigen, die es vorher nicht
gab, Räume und Formen abbilden, die in Wirklichkeit nicht existieren und nie existieren werden.
Um etwas abbilden zu können, muß man aber zunächst eine Möglichkeit finden, es überhaupt
zu beschreiben. Selbst simpelste Gegenstände haben eine innere Logik, es ist nicht damit getan, ihre Form einfach abtasten zu lassen, ihre Oberfläche Punkt für Punkt einzulesen, um sie in
den virtuellen Raum aufzunehmen. Daher begann man zunächst damit, nur einfachste Objekte zu
verwenden, Kugeln, Quader und Zylinder, so waren die ersten virtuellen Welten von ganz einfachen Gegenständen bevölkert. Kompliziertere Gegenstände erhielt man dadurch, daß man sie aus
einfacheren zusammensetzte, zum Beispiel dadurch, daß man Gegenstände verschweißt oder voneinander abzieht, wie bei einer Hantel als Zylinder mit Kugeln an den Enden, oder einem Becher
als großem Zylinder, von dem ein kleinerer Zylinder abgezogen wird.
Wie aber beschreibt man Gegenstände, die sich nicht als eine Ansammlung von Elementarobjekten betrachten lassen? Man reduziert die Objekte zunächst auf das Wesentliche. Man abstrahiert von massiven Gegenständen und betrachtet nur noch noch ihr Äußeres, die Form, die
man darstellen möchte: Ihre Oberfläche. Der nächste Schritt ist also, statt der Gegenstände selber
nur noch durch ihre Oberfläche zu beschreiben, aber nicht als Punktmasse, sondern man sucht
nach Nähten, nach Knicken in der Oberfläche, zwischen denen kontinuierliche, glatte Bereiche
liegen. Dies führt zur Theorie der Freiformflächen und -kurven, wo man eine kompakte Beschreibung für Oberflächenstücke sucht und nach Möglichkeiten, diese einzelnen Stücke, Patches, an
Nähten zu verschweißen und so zu einer geschlossenen Hülle zusammenzusetzen. Auf diese Weise sind viele verschiedene Darstellungen von Oberflächentypen entstanden, Freiformkurven und
Freiformflächen als Gegenstand der Geometrischen Modellierung wurden die Grundelemente zur
rechnerinternen Darstellung virtueller Gegenstände.
Modelliert man heute Gegenstände mit einem gängigen Computerprogramm zum Bauen virtueller Räume, einem Modeller, so bietet sich ein ganzer Zoo von Kurven, Flächen und Elementargegenständen, die man miteinander kombinieren kann, um eine virtuelle Szene einzurichten.
1
2
KAPITEL 1. EINLEITUNG
Ein Modeller bietet dazu einen ganzen Werkzeugkasten von Hilfsmitteln, derer man sich bedienen kann, um für seine Räume Schränke zu bauen und Tische, Stühle und Lampen. Architekten
entwerfen ganze Häuser am Rechner, Ingenieure konstruieren präzise technische Bauteile. Ist ein
Gegenstand einmal definiert, kann man ihn in seiner Szene so oft kopieren, wie man möchte, ihn
an verschiedenen Stellen in verschiedener Größe darstellen, verzerren, variieren und weiterbehandeln.
Man hat sich also einen großen Schritt entfernt davon, Gegenstände als Netze von Punktmassen zu beschreiben und Elementarobjekte zu gruppieren; Freiformflächen sind zur Standardform
der Beschreibung von Gegenständen geworden. Einem Computergafiker bieten sich neben der Benutzung eines Modellers noch viele andere Darstellungsmittel. Er kann in eigenen Programmen
Softwarebibliotheken verwenden, denen er Freiformflächen zur interaktiven Darstellung übergibt,
es existieren Dateiformate, in denen er die Oberfläche von Gegenständen nicht nur als Punktnetz,
sondern als Freiformflächen speichern oder über ein Netz zu anderen Rechnern schicken kann,
und Programme, die ferngesteuert Transformationen auf Objekte anwenden können und das Ergebnis zurückschicken, und schließlich gibt es Szenenbeschreibungssprachen, in denen ein ganzer
virtuelle Raum in hierarchischer Form zusammen mit Ereignissen und Animationsabläufen beschrieben werden kann.
Andererseits hat die Beschreibung virtueller Welten auf dieser Ebene auch gravierende Nachteile. Je komplizierter eine Szene wird, je detailreicher die Gegenstände, umso deutlicher wird es,
daß man eine Beschreibung auf einem noch höheren Niveau braucht, um diese Welten handhabbar
zu halten, um nicht buchstäblich in einem Meer von Flächenstücken zu ertrinken. – Das bedeutet, man sucht nach einer Möglichkeit, die Einflußmöglichkeiten auf eine Handvoll der richtigen
Regler zu beschränken, die es einem ermöglichen, mit einem Griff einem Hochhaus ein weiteres
Stockwerk zu verpassen, sämtliche Fenster mit einem Schlag auszuwechseln und zu bestimmen,
daß statt eines Flachdaches nunmehr ein Sattel- oder ein Mansardendach zu verwenden ist. Balkone sollen nicht mehr eckig und kantig aussehen, sondern man gibt ornamentierte Träger vor, die sie
stützen, und die an jeder Stelle individuell, aber nach festen Regeln in die Fassade zu integrieren
sind.
Um dies zu erreichen, ist ein Modellierer heute gezwungen, selber zu programmieren. Er muß
ein Programm schreiben, das ein Modell erzeugt, er kann sich einer Softwarebibliothek bedienen,
die ihn in sofern unterstützt, als sie eine Schnittstelle zur Anzeige bestimmter Darstellungsprimitive zur Verfügung stellt. Will man ein Modell programmieren können, also in eigener Weise
parametrisieren und regelgesteuert erzeugen, so ist man gezwungen, selber zu programmieren, um
aus gegebenen Parametern Objekte zu machen, die von der Schnittstelle des verwendeten Anzeigemoduls verarbeitet werden können. Im einfachsten Fall sind das nur Listen von Dreiecken im
Raum, im günstigeren Fall bietet die Schnittstelle die Möglichkeit, eine Reihe von Freiformflächen zu übergeben, oder sie ist gar in der Lage, verschiedene Grundprimitive darzustellen, sodaß
man Kugeln oder Zylinder nicht erst in Einzelflächen zerlegen muß.
Eine Beschreibungssprache für Modelle auf einem höheren Niveau aber hat sich bisher noch
nicht durchgesetzt, und es scheint eine große Herausforderung zu sein, die richtige Sichtweise, die
passende Abstraktion für eine solche Darstellung zu finden.
Die Generative Modellierung könnte einen wichtigen Schritt in diese Richtung bedeuten. Die
Grundidee dazu geht zurück auf ein Buch von John M. Snyder, in dem er beschreibt , wie nicht
mit Objekten, sondern mit Abbildungen modelliert wird. Das bedeutet, die oben entworfene regelgeleitete Konstruktion von Gegenständen kann tatsächlich expliziert werden, man erhält eine
Möglichkeit, den Konstruktionsprozeß selber zu beschreiben und zu parametrisieren statt nur das
Objekt, das schließlich entsteht.
Im folgenden soll dazu im einzelnen gezeigt werden, in welcher Weise man diese Idee als
3
konsequente Weiterentwicklung der bestehenden Technologie ansehen kann. Eine Entwicklung
in diese Richtung scheint “in der Luft zu liegen”, wenn man die Entwicklung der Modellierung
betrachtet und die verwendeten Modeller vergleicht, insbesondere im Hinblick auf die neueste
Generation der parametrischen Modeller.
Im dritten Kapitel werden die Arbeiten vorgestellt, die als Basis für die vorliegende Implementation der Generativen Modellierung dienen. Zum einen wird der theoretische Hintergrund
erläutert sowie der Weg beschrieben, den Snyder gegangen ist, um die Idee des Modellierens mit
Geometrieerzeugenden, mit Generatoren, zu realisieren, zum anderen wird die Softwareumgebung
erläutert, die der vorliegenden Arbeit zugrundeliegt. In der Abteilung für Computergrafik der Universität Bonn ist der MRT als objektorientierte Programmierplattform entstanden, in die viele
computergrafische Verfahren zur Darstellung von Szenen, dem Rendering, eingeflossen sind. Ist
ein virtueller Raum gegeben, kann man mit Hilfe des MRT Bilder von fotorealistischer Qualität erzeugen und dabei auf viele Aspekte der Darstellung Einfluß nehmen, spiegelnde und transparente
Objekte darstellen, Lichtquellen durch Rauch scheinen oder eine Szene indirekt beleuchten lassen,
man kann auf die Oberfläche von Gegenständen Bilder kleben und Objekte aus lichtdurchlässigem
Material zeigen, die in ihrem Innern gefärbt sind.
Dieser Arbeit nun lag die Idee zugrunde, eine Grundlage zu schaffen, um die Generative Modellierung in den MRT zu integrieren. Das Endziel ist also, eine neue Modellieridee mit der fotorealistischen Darstellung modellierter Gegenstände zu kombinieren.
Diese große Aufgabe wurde auf zwei Wegen angegangen. Einmal ist rein technisch die Infrastruktur geschaffen worden, um Abbildungen in der notwendigen Weise verketten zu können, und
zwar auf Grundlage eines objektorientierten Designs. Dazu wurden dann die Objekte der Modellierung, Freiformkurven und -flächen, implementiert, und daraus schließlich Abbildungsobjekte
gewonnen, mit denen auf diesen Objekten operiert werden kann. Das System gestattet es damit,
Funktionen zu schreiben, die Abbildungsobjekte erzeugen, mit denen Freiformobjekte in flexibler
Weise manipuliert werden können, und eine Reihe von Beispielen zeigt, wie variabel diese eingesetzt werden können. Damit ist ein Fortschritt gegenüber reinen Anzeigebibliotheken wie oben
beschrieben erzielt worden, denn ein Benutzer muß zwar immernoch programmieren, um zu modellieren, aber er muß nicht mehr auf der untersten Ebene ansetzen und Einzelflächen definieren,
sondern er hat die Möglichkeit, ein parametrisiertes Objekt zu definieren. Ein Stuhl ist nicht mehr
eine Ansammlung von Freiformflächen, sondern ein Werkzeug, der Benutzer spezifiziert nurmehr
ein Skelett eines Stuhles, und die zusammengesetzte Abbildung konstruiert für ihn daraus die
Einzelflächen, die den eigentlichen darstellbaren Stuhl definieren.
Der implementierte objektorientierte Mechanismus gestattet es zudem, zusammengesetzte Abbildungen zur Laufzeit eines Programms zu definieren, denn Abbildungen sind nun Objekte, die
angelegt, miteinander verbunden und wieder gelöscht werden können. Das ist gerade der entscheidende Punkt bei der Modellierung, ein Benutzer ist nicht mehr gezwungen, spezielle Funktionen
zu schreiben, die eine Softwarebibliothek mit den Datenstrukturen versorgt, die sie als Anzeigeprimitive verlangt, sondern er kann eine Funktion schreiben, um die Konstruktion selber zusammenzusetzen.
Das entstandene Softwarepaket ist also dazu prädestiniert, weniger als Programmierschnittstelle für Anwendungsprogrammierer zu dienen, sondern vielmehr in ein Programm integriert zu
werden, mit dem Konstruktionen zur Laufzeit beschrieben werden können. Dies kann prinzipiell
auf zwei Weisen geschehen: Einmal in Form eines interaktiven Modellers, zum anderen befehlsgesteuert. Solche Befehle kann man in einer Datei speichern, die als Programmiersprache für
Geometrische Modellierung beschreibt, wie einerseits die allgemeinen Werkzeuge und andererseits die speziellen Parameter aussehen, die für ein bestimmtes Modell verwendet werden sollen.
Der Benutzer ist also nicht mehr auf die feste Menge von Werkzeugen angewiesen, die ihm vom
KAPITEL 1. EINLEITUNG
4
Modeller serviert werden, sondern er kann modellspezifische Werkzeuge entwerfen, in einer Datei
speichern, aus einer Datei einlesen und zu anderen Rechnern schicken.
Im zweiten Teil dieser Arbeit wurde daher nach einem Sprachkonzept gesucht, das geeignet ist, als Beschreibungssprache und Dateiformat für zusammengesetzte Abbildungen zu dienen,
als Programmiersprache für Geometrische Modellierung. Dabei hat sich gezeigt, daß das imperative Sprachparadigma bestimmten Beschränkungen unterliegt, die eine prinzipielle Grenze
darstellen. Als dem Problemfeld wesentlich besser angepaßt erscheint dagegen das funktionale
Programmierparadigma, das sich dadurch auszeichnet, daß die fundamentale Operation des Modellierens mit Abbildungen, die Komposition, integraler Bestandteil einer funktionalen Sprache
ist. Es wurde daher eine Minimalimplementation der wesentlichen Kurven und Flächen in einer
funktionalen Sprache durchgeführt. An Hand einer Reihe von Beispielen kann sodann demonstriert werden, daß sich eine solche Sprache sehr gut zur Beschreibung parametrisierter Modelle
eignet, denn relativ komplizierte Objekte lassen sich in wenigen Zeilen in verständlicher Weise
definieren.
Diese Arbeit hat also zwei wesentliche Ergebnisse. Zum einen fand eine Übertragung der Idee
der Generativen Modellierung auf eine C++-Klassenbibliothek statt, die wesentliche Anforderungen objektorientierten Designs erfüllt, und zwar Erweiterbarkeit und modularen Aufbau. Zum
anderen wurde eine Beschreibungsmöglichkeit für solche Generativen Modelle gefunden, die wesentliche Hinweise auf die Frage liefert, wie die Abbildungsbibliothek erweitert werden muß, um
tatsächlich als Implementation einer Art Programmiersprache für Geometrisches Modellieren zu
dienen.
Der Brückenschlag zwischen beiden Teilen steht allerdings noch aus. Es ist nicht klar, inwieweit die implementierte C++-Bibliothek von Abbildungen, das Genmod-Paket, in Richtung
einer funktionalen Programmiersprache ausgebaut werden muß, um in der Lage zu sein, in der
gewünschten Weise zu modellieren. Einige Hinweise auf mögliche Antworten werden in der abschließenden Zusammenfassung gegeben.
Im folgenden wird der Verlauf der Argumentation in den folgenden Kapiteln Arbeit grob umrissen, um einen Leitfaden zu liefern.
Heutige Modelle sind unzureichend parametrisierbar
Mit steigenden Anforderungen an virtuelle Welten und komplexe Modelle werden zwei
Designziele bei der Entwicklung von Modellern und Modellbeschreibungssprachen immer
wichtiger, und zwar die Änderbarkeit und die Wiederverwendbarkeit von gefundenen
Lösungen für Modellierprobleme.
Objektorientierte Modeller unterliegen zwei wesentlichen Einschränkungen
Ein rein objektorientierter Modeller bietet die Möglichkeit, Objekte in einer Szene zu plazieren und die Größe und Form der Objekte solange zu modifizieren, bis eine Szene den
Anforderungen genügt. Diese Arbeitsweise ist bei komplizierten Szenen mit vielen Objekten, die nach bestimmten Regeln variiert werden sollen, sehr mühsam und unnatürlich.
– Verlust der Konstruktionsgeschichte
Will man eine Reihe ähnlicher Objekte erzeugen, deren Konstruktionsprozeß sich nur
jeweils an ganz bestimmten Stellen unterscheidet, so ist man gezwungen, ab der Stelle, wo man ändern möchte, jedesmal alle Konstruktionsschritte leicht abgeändert zu
wiederholen.
– Verlust funktionaler Abhängigkeiten
Viele der Größen, die ein Modell definieren, hängen von anderen Größen ab. Ändert
5
sich ein Teil eines Modells, so hat das möglicherweise Auswirkungen auf andere Teile, und es muß bei Änderung viel nachgebessert werden, obwohl man vielleicht eine
Regel angeben kann, wie Größen eines Modells voneinander abhängen oder zu ändern
sind: Halbiere die Breite von jedem zweiten Fenster dieses Hochhauses.
Die erste Einschränkung kann mit parametrischen Modellern behoben werden
Parametrische Modeller ermöglichen es, die Konstruktion eines Objektes mitzuschneiden
und nachträglich auf die angewandten Veränderungen Einfluß zu nehmen, Parameter zu
variieren und diese Variationen sogar zu animieren. Das wird ermöglicht durch eine Erweiterung des objektorientierten Paradimas.
Die Aufhebung der zweiten Einschränkung führt zum Generativen Modellieren
Will man mit wenigen Parametern Einfluß auf eine Konstruktion nehmen, so muß spezifizierbar sein, in welcher Weise diese Parameter in die Konstruktion einfließen. Das bedeutet,
man muß den Datenfluß beschreiben können, um zu explizieren, durch welche Berechnungen welche Größen des Modells aus den Eingabeparametern hervorgehen. Die Konstruktion
des Modells kann dann für jeden Satz von spezifizierenden Eingaben neu durchgeführt werden.
Generative Modelle lassen sich funktional beschreiben
Das geeignete Konzept für die Realisierung der Generativen Modellierung ist die Abbildung. Das Modellieren mit Abbildungen erfordert es, höhere Funktionen zu verwenden
sowie an jeder Stelle bezug nehmen zu können auf sämtliche bereits definierten Objekte.
Beides ist bei einer funktionalen Sprache von vornherein gegeben.
Es ist das Ziel dieser Arbeit, einen Weg zu weisen, wie Benutzer von kommerziellen Modelliersystemen in die Lage versetzt werden können, ihr spezifisches Wissen über ihr Arbeitsgebiet
innerhalb der dreidimensionalen Modellierung in Problemlösungen umzusetzen, die auf hohem
Niveau änderbar und wiederverwendbar sind. Eine solche Lösung brächte Modellierern aller Bereiche einen dramatischen Zuwachs an Arbeitsproduktivität.
6
KAPITEL 1. EINLEITUNG
Kapitel 2
Evolution der Geometrischen
Modellierung
Die Computergrafik als Teilgebiet der Informatik beschäftigt sich mit der Synthese von Bildern
und Bildsequenzen und den theoretischen Fragestellungen, die aus dem zunehmenden Einsatz bei
wissenschafticher Visualisierung, in der Werbung und bei der Realisierung von Spezialeffekten in
Spielfilmen erwachsen.
Geometrische Modellierung ist der Zweig der Computergrafik, bei dem es um die Behandlung
geometrischer Objekte in Datenverarbeitungsanlagen geht, um ihre Repräsentation mit Hilfe entsprechender Datenstrukturen und die Berechnung geometrischer Größen mit effizienten Algorithmen. Dieses Gebiet ist von zunehmender praktischer Relevanz, weil die Zahl der Anwendungen,
die sich einer dreidimensionalen Darstellung bedienen, sprunghaft steigt, wobei einerseits die Größe der Modelle zunimmt als auch andererseits die Komplexität der abgebildeten virtuellen Räume.
Damit gewinnt auch die Schnittstelle zwischen der Maschine und dem Anwender an Bedeutung
und bestimmt zum Teil maßgeblich den Aufwand, den die Modellierung eines bestimmten Raumes
oder Gegenstands bedeutet.
Untersucht man die Evolution der Ansätze, die den dabei benutzten Modelliersystemen zugrundeliegen, und betrachtet die Arbeitsweise der Anwender beim Lösen von Modellieraufgaben,
so ergibt sich dadurch die Möglichkeit, Entwicklungstrends abzulesen und besser zu verstehen,
was das Wesen der dreidimensionalen Modellierung ausmacht.
Um das Arbeitsgebiet einzugrenzen und die nötige Terminologie einzuführen, werden im folgenden einige der wesentlichen Konzepte vorgestellt, die im Rahmen der Computergrafik und der
Geometrischen Modellierung entwickelt worden sind.
2.1 Die Produktion einer Animation
Die Produktion eines computergenerierten Filmes läuft in einer Weise ab, die mit der Vorgehensweise bei einer konventionellen Filmproduktion vergleichbar ist. Die einzelnen Einstellungen
werden von einem Operator, dem Modellierer, am Computer mit Hilfe eines Modelliersystems
erstellt. In der Terminologie der Computergrafik nennt man ein solches System einen Modeller.
Die Produktion von der künstlerischen Idee bis zum fertigen Film ist ein vielstufiger Prozeß. Im
folgenden werden die wesentlichen Schritte einer solchen Produktion beschrieben, um die Probleme zu charakterisieren, die solche Anwendungen mit sich bringen, und um eine Einordung des
Beitrages dieser Arbeit zu ermöglichen.
Unabhängig von der Anwendung, seien es Spezialeffekte für einen Spielfilm, eine Visuali7
8
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
sierung wissenschaftlicher Foschungsergebnisse, die Konstruktion von Bauteilen in der Industrie
oder von Bauten in der Architektur mit abschließender dreidimensionaler Darstellung, oder ob
es darum geht, eine virtuelle Welt auf einem verteilten System zu bauen, stets wird die gleiche
Abfolge von Produktionsschritten durchlaufen, mit verschiedenen Gewichtungen je nach Gebiet.
Der Modellierer hat zunächst die Aufgabe, einen virtuellen Raum zu erstellen. Diese Bezeichnung rührt daher, daß mit zunehmender Komplexität der Anwendungen nicht mehr nur einzelne Gegenstände1 , englisch Solids, modelliert werden wie im klassischen computerunterstützten Konstruieren, dem CAD (’computer aided design’), sondern extrem detailreiche Innenräume,
Straßenzüge und ganze Landschaften, die nur im Rechner existieren. Um die Geometrie einer
solchen Szene effizient zu erstellen, kann sich der Modellierer einer Bibliothek von vordefinierten
geometrischen Grundkörpern und bereits modellierten Gegenständen bedienen, oder er kann selber neue Gegenstände konstruieren. Der Begriff Gegenstand bezeichnet dabei pauschal jede Form
eines dreidimensionalen Objektes, seien es nun Möbel, technische Bauteile oder ganze Bauwerke,
die man in einer Sequenz synthetischer Bilder, einer Animation, zeigen möchte.
Bei der Szenenkonstruktion manipuliert der Modellierer die vom 3D-Modeller gezeigte dreidimensionale Ansicht der Gegenstände. Die Konstruktion vieler Modelle geht dabei von zweidimensionalen Schnitten oder Grundrissen aus, ein 3D-Modeller kann daher als eine Erweiterung
eines 2D-Modellers angesehen werden. Im weiteren wird von Modellierung die Rede sein, wenn
zwei- oder dreidimensionale geometrische Modellierung gemeint ist.
Als Eingabegeräte für einen interaktiven Modeller dienen neben der Tastatur zur Eingabe von
Punkten auf dem zweidimensionalen Bildschirm noch Geräte wie die Maus bzw. der Trackball,
Digitizertabletts oder Lichtgriffel. Mit Stellrädern, englisch ’paddles’, können Werte auf einer
eindimensionalen Skala manipuliert werden. Stellvertretend für diese Zeigegeräte, die in [Fel92]
detailliert beschrieben werden, wird im folgenden von der Maus gesprochen.
Ist die Form der Gegenstände, ihre Geometrie also, eingegeben, werden den Objekten Materialien zugewiesen, das heißt, eine Farbe, eine Musterung und das Reflexionsverhalten der Oberfläche
der Gegenstände werden definiert. Ein Tisch erhält beispielsweise eine Holzmaserung, ein Becher
soll aussehen, als wäre er aus glänzendem Metall, und ein Glas bekommt die Zusatzinformation,
daß es getönt transparent ist. Diese Gegenstände werden dann in der Szene plaziert. Damit wird die
sogenannte Szenenhierarchie festgelegt: Der Tisch wird neben ein Regal gestellt, das Glas oder
der Becher auf den Tisch, und die einzelnen Böden des Regals werden mit Büchern gefüllt. Die
Informationen im Szenengraphen, der der Szenenhierarchie zugrundeliegt, legen somit fest, welche Gegenstände in bezug auf andere Gegenstände plaziert werden. Dies hat den Vorteil, daß zum
einen die Geometrie der Gegenstände getrennt ist von ihrer Verwendung in einer Szene, zum anderen erhält man so eine Gruppierung; man kann also etwa den Tisch mit den Gläsern an eine andere
Stelle setzen, ohne daß jedes Glas einzeln bewegt werden müßte. Die Szenenhierarchie benutzt
dabei nur Verweise auf Gegenstände, Referenzen, so kann man zum Beispiel mehrere Gläser auf
den Tisch setzen, ohne daß man tatsächlich mehrere Gläser modellieren müßte. Schließlich wird
die Definition einer Szene noch durch das Aufstellen einer Reihe von Lichtquellen vervollständigt,
um die Gegenstände auch zu beleuchten. Die Position der Lichter ist ebenfalls in der Szenenhierarchie festgehalten, so erhält man etwa eine Stehlampe durch Gruppierung eines geometrischen
Objektes, des Lampenfußes, mit einer Lichtquelle.
Der Szenengraph enthält also eigentlich eine Reihe von Koordinatentransformationen, mit denen der Übergang von einem lokalen Koordinatensystem eines Gegenstands – oder einer Gruppe
von Gegenständen – auf ein übergeordnetes Koordinatensystem beschrieben wird. Das Bezugssystem, in das die gesamte Szene eingebettet ist, heißt das Weltkoordinatensystem, kurz WKS,
1 In der Literatur ist zumeist von “Objekten” die Rede. In dieser Arbeit wird der Begriff “Gegenstand” verwandt, um
Verwechslungen zu vermeiden, die durch den vielfach überladenen Begriff Objekt entstehen könnten.
2.1. DIE PRODUKTION EINER ANIMATION
9
im Gegensatz zum LKS der einzelnen Gegenstände. Läßt man diese Positionsinformationen im
Szenengraphen zusätzlich von einer simulierten Zeit abhängen, so bewegen sich die Gegenstände,
ein Glas auf dem Tisch fällt um oder eine Murmel kullert einen Regalboden entlang.
Um schließlich die Abbildung einer Szene auf ein Bild oder eine Bildsequenz zu erhalten, wird
abschließend eine virtuelle Kamera in der Szene plaziert. Mit Hilfe eines Renderingverfahrens
wird aus der Szene und der mit der Kamera definierten Projektion schließlich ein Bild gewonnen.
Es gibt dabei für das Rendering zwei grundsätzlich verschiedene Ansätze:
Approximationsbasiertes Rendern.
Die Oberfläche sämtlicher Gegenstände in einer Szene wird in ein Dreiecks- oder ein Polygonnetz umgewandelt. Moderne Grafikhardware ist in der Lage, große Mengen gefärbter
3D-Dreiecke schnell anzuzeigen, sodaß ein interaktives Arbeiten mit mehreren Frames pro
Sekunde möglich ist. Die approximative Darstellung ist also Basis für die 3D-Sicht, die ein
Modeller während einer Konstruktion präsentiert.
Strahlschußbasiertes Rendern.
Beim Raytracing-Verfahren wird der Weg von Lichtstrahlen durch die Szene simuliert. Dazu
ist es nötig, den Schnitt eines Strahls mit einem Gegenstand schnell ausrechnen können,
weil oft Millionen solcher Schnitte berechnet werden müssen, um ein Bild zu erzeugen.
Dafür kann man auf diese Weise fotorealistische Ergebnisse erzielen, weil Phänomene wie
Spiegelungen und Transparenz berücksichtigt werden.
Mit Hilfe des Raytracing kann man physikalisch gesprochen den Lichttransport von spekulären zu spekulären und von spekulären zu diffusen Oberflächen modellieren. Neuere Entwicklungen des Rendering berücksichtigen aber auch andere Arten des Lichttransportes und sind motiviert
von allgemeinen Theorien über die Ausbreitung elektromagnetischer Wellen.
Die beiden Sichtweisen einer Oberfläche, einerseits als Polygonapproximation und andererseits als zweidimensionale Menge von Punkten im Raum, sind auch für viele andere Verfahren der
Computergrafik grundlegend. In einem speziellen Teilgebiet, der ’computational geometry’, ist
man an schnellen Algorithmen auf linearen Strukturen interessiert, man betrachtet etwa Mengen
von Streckenzügen, Schnitte von Polygonen oder Polyedern oder die Bestimmung von konvexen
Hüllen. Im ’computer aided geometric design’ (CAGD) dagegen beschäftigt man sich insbesondere mit Freiformflächen, wo man Konzepte wie die Geometrische Stetigkeit und Fragestellungen
wie das n-Patch-Problem entwickelt. Diese Gebiete werden im folgenden Abschnitt 2.2 noch näher
erläutert.
Die Verwendung von Gegenständen stellt also gewisse Anforderungen an die rechnerinterne
Darstellung einer Szene mit ihrer Geometrie und die dabei eingesetzten Datenstrukturen. Diese
interne Darstellung der Geometrie ist das Modell des Gegenstands und kann ihrerseits aus einer komplexen Hierarchie von Datenstrukturen bestehen. In der Wahl eines bestimmten Modells
kommt aber bereits zum Ausdruck, welcher Ansatz bei der Modellierung benutzt wird und welches Modellierparadigma einem Modeller zugrundeliegt. Der Vergleich solcher Ansätze ist Teil
der vorliegenden Arbeit.
Umwandlung in eine Approximation sowie der Strahlschnitt sind elementare Operationen, die
jedes Modell unterstützen sollte. Abstrakter ausgedrückt sind die Grundoperationen eines Modells einerseits das Sampling einer zweidimensionalen Punktmenge im Raum und andererseits
das Lösen von Nullstellenproblemen auf dieser Menge.
10
2.2
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
Konzepte der Modellierung
Die Computergrafik hat eine reiche Fülle von verschiedenen Ideen zur Darstellung von dreidimensionalen Objekten hervorgebracht. Diese Vielfältigkeit liegt in dem Bedürfnis begründet, die verschiedensten Arten von natürlichen Formen mitsamt ihres Verhaltens zu simulieren. Zunächst gibt
es einfache geometrische Grundkörper wie Quader oder die platonischen Polyeder wie Tetraeder
oder Ikosaeder. Andere Grundkörper haben gekrümmte Oberflächen, beispielsweise Kugeln, Kegel oder Zylinder. Eine Verallgemeinerung dieser gekrümmten Objekte führt zu Freiformflächen
wie Bézier- oder Splineflächen. Basis für viele Freiformflächen sind Freiformkurven, Kurven im
Raum, die dann zu Flächen kombiniert werden können, etwa zu interpolierenden Coons-Flächen.
Kurven und Flächenelemente, Patches, stellen heute die Grundelemente der gängigsten Modeller dar. Es sind vielfältige Operationen zur Kombination dieser Elemente vorhanden. Verallgemeinert man den Zylinder, indem man statt eines Kreises beliebige geschlossene ebene Kurven
als Querschnitt zuläßt, erhält man ein die Extrude-Operation. Verallgemeinert man weiter, indem
man eine beliebige Querschnittsfläche eine beliebige Raumkurve entlangzieht, so stellt das überstrichene Volumen ein Sweep-Objekt dar. Aus zwei parametrischen Raumkurven c0 ; c1 erhält man
eine Loft-Fläche, indem korrespondierende Parameterwerte mit einer Strecke verbunden werden,
die resultierende Funktion p(u; v) = uc0 (v) + (1 , u)c1 (v) ist ein parametrisches Patch. Allgemein
sind Freiformpatches in parametrischer Darstellung also Abbildungen p : (u; v) 7! (x; y; z) vom
Zweidimensionalen ins Dreidimensionale.
Schließt man einen Zylinder mit einer Kugel ab, deren Radius größer ist als der Zylinderradius, so erhält man an der Verbindung einen Knick. Blending beschäftigt sich mit der knickfreien
Verbindung von Flächenstücken. Eine zweifach stetige Verbindung zwischen Patches erhält man
beispielsweise, wenn man sich vorstellt, eine kleine Kugel würde einen solchen Knick entlangrollen. Die umgekehrte Operation, beispielsweise das Abrunden oder Abschrägen von Kanten eines
Würfels, ist das Bevelling.
Legt man um jeden Punkt einer Kurve eine Kreisscheibe von gegebenem Radius und betrachtet
die Vereinigung dieser Mengen, so ist die Randkurve dieses Gebietes eine sogenannte Offsetkurve. Entsprechend kann man die Offsetfläche zu einem Patch oder einer Kurve betrachten, die Oberfläche des Volumens, das aus der Vereinigung von Kugeln um Punkte der Fläche oder der Kurve
entsteht. Solche Konstruktionen benutzt man etwa, um die Bahn eines kugelförmigen Fräskopfes
zu verfolgen und zu bestimmen, welches Volumen aus einem anderen Körper herausgeschnitten
würde.
Viele Gegenstände im täglichen Leben und vor allem in industriellen Anwendungen sind Kombinationen aus einfacheren Körpern. Die Technik der constructive solid geometry (CSG) beruht
auf Boole’schen Operationen von Grundkörpern. So kann man einen CSG-Baum definieren, in
dem die Knoten neue Objekte sind, die durch Schnitt, Vereinigung oder Differenz von anderen
Körpern bestehen, um ein Bauteil aus verschiedenen Teilen zusammenzusetzen. Eine Bohrung simuliert man dann, indem man von einem Gegenstand verschiedene Zylinder abzieht, die in ihn
hereinragen, oder eine Fräsung, indem man ein Fräsvolumen wie oben beschrieben von einem
Gegenstand subtrahiert.
Eine weitere Verallgemeinerung erhält man, wenn man verformte Gegenstände betrachtet. Mit
Deformationen kann man Gegenstände verdrehen oder verbiegen, dies kann auch zeitabhängig
geschehen oder bei ’space warps’ in Form einer räumlichen Deformationswelle.
Während diese Konzepte zum gängigen Repertoire von modernen Modellern gehören, gibt es
noch viele weitere Anwendungen, die in spezielleren Forschungsgebieten behandelt werden. Um
beispielsweise Vielteilchenphänomene zu simulieren, sei es ein Feuerwerk, Regen oder Schnee,
benutzt man Partikelsysteme. Partikel haben eine Position im Raum, gegebenenfalls eine Masse,
2.2. KONZEPTE DER MODELLIERUNG
11
eine Orientierung, eine Farbe und ein Alter. Sie werden von Kraftfeldern beeinflußt, dadurch kann
man Wirbel oder Ströme simulieren, indem die entsprechenden Bewegungspfade, Trajektorien,
über die Zeit berechnet werden. Interagierende Partikel kann man zudem dazu benutzen, um
Massepunkte zu Netzen zu verbinden und so Textilien zu simulieren oder Kraftfelder auf Teile
von Oberflächen wirken zu lassen.
Physikalisch basiertes Modellieren (PBM) nutzt Techniken wie inverse Kinematik, um Gelenkmechanismen (IK-Chains) zu beschreiben. Bewegt man einen sogenannten Endeffektor wie
das Ende eines Roboterarms und läßt es eine bestimmte Raumkurve beschreiben, so werden die
nötigen Bewegungen der Gelenke berechnet und der Mechanismus nachgeführt. Mit einer Erweiterung dieser Technik lassen sich auch ganze Skelette bewegen, so kann man einen Zweibeiner
einen Weg entlanggehen lassen, einfach indem man eine Fußspur vorgibt.
Im CAGD wurden für Freiformkurven und -flächen viele verschiedene Darstellungen gefunden, die zumeist auf Polynomen oder stückweise definierten Polynomen beruhen, zwischen denen eine gewisse Stetigkeit verlangt wird. Neben Bézier- und B-Spline-Darstellungen kommen
dort NURBS (Non-Uniform Rational B-Splines) oder andere allgemeiner parametrisierte Basisfunktionen zum Einsatz. Basisfunktionen sind wichtig, um den Verlauf von Funktionen mit Hilfe
von Kontrollpunkten auf eine intuitive Weise festzulegen, wenn man die Basisfunktionen als
Koeffizienten der Kontrollpunkte betrachtet. Kurven kann man mit dem Konzept der Tensorproduktdarstellung zu rechteckigen Flächen kombinieren. Daneben benutzt man dreieckige Flächenstücken und n-sided patches, mehrseitigen Füllflächen, um Patches möglichst flexibel zu größeren
Verbänden zusammensetzen zu können. Daraus ergibt sich die Fragestellung, welche Übergangsbedingungen Patches an ihren Rändern zu erfüllen haben. Ein hilfreiches Konzept ist in diesem
Zusammenhang die Geometrische Stetigkeit, eine Formstetigkeit höherer Ordnung, die von einer
spezifischen Parametrisierung unabhängig ist.
Neben der parametrischen Darstellung p : (u; v) 7! (x; y; z) von Freiform-Flächen kann man
auch implizite Flächen betrachten. Diese erhält man als eine zweidimensionale Menge von Nullstellen einer Funktion f : R3 ! R, also als Isofläche der Form f(x; y; z) 2 R3 j f (x; y; z) = 0g. Bei
der Metaball-Technik werden einzelne Raumpunkte mit einem Potential umgeben, die Summe
der Potentiale ergibt dann eine Dichtefunktion. Man bestimmt den Schnitt eines Strahls p(t ) =
p0 + tv mit der Isofläche zum Schwellwert d durch eine iterative Nullstellensuche für die reelle Funktion q(t ) = f ( p(t )) , d. Verschießt man Strahlen, die parallel zu den Koordinatenachsen
verlaufen, und bestimmt sämtliche Schnittpunkte, so erhält man mit Hilfe des ’marching cube’Algorithmus eine Approximation der Isofläche. So erfüllen implizite Flächen beide elementaren
Anforderungen an Modelle.
Neben impliziten Flächen kann man auch implizite Kurven betrachten. Diese erhält man oft
auf natürliche Weise, wenn man Schnittprobleme betrachtet. Sind s0 und s1 beispielsweise Flächen
in expliziter, das heißt parametrischer Form, so ist die Schnittkurve beider Flächen als die Menge
der Parameterwerte (u0; v0; u1; v1 ) gegeben, für die gilt: s0(u0; v0 ) = s1 (u1; v1). Ebenso läßt sich
das Strahlschnittproblem für solche Flächen als die Menge der (u; v; t ) formulieren, für die gilt
s(u; v) = p0 + tv. Dabei ist im übrigen bemerkenswert, daß das Strahlschnittproblem für implizite
Flächen zu einem eindimensionalen und das für parametrische Flächen zu einem dreidimensionalen Nullstellenproblem führt, also im allgemeinen schwerer zu lösen ist.
Ein grundsätzliches Problem ist nun der Wechsel von impliziter zu parametrischer Form. Dabei
sucht man etwa die parametrische Darstellung einer Schnittkurve zweier Flächen. Das kann einmal die Raumkurve c sein, die Menge der Punkte des R3 , die beide Flächen gemeinsam enthalten,
oder andererseits eine Kurve c0 im Parameterraum einer der Flächen, sodaß man die Raumkurve
durch Komposition c = s0 c0 erhält. Die Auflösung einer impliziten Gleichung geschieht aber in
der einen oder anderen Form immer durch ein Iterationsverfahren und ist entsprechend aufwendig.
12
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
Es verbietet sich also, alle Punkte einer impliziten Kurve durch Iteration auszuwerten. Um dies zu
umgehen, benutzt man Kurveninterpolation: Man berechnet nur einige Punkte des Schnittes und
interpoliert zwischen diesen. Eine solche Näherungslösung gilt aber nur mit einer bestimmten Genauigkeit, und es gibt durchaus Anwendungen, wo die benötigte Genauigkeit nicht im Vorhinein
bekannt ist. Fährt die Kamera im Laufe einer Animation immer näher an eine Schnittkurve heran,
so kann die lokal benötigte Genauigkeit sprunghaft ansteigen. Wegen dieser Schwierigkeiten ist
der Übergang zwischen impliziter und parametrischer Darstellungen noch ein aktives Forschungsgebiet, und die Lösung hängt zunächst noch von der spezifischen Problemstellung der avisierten
Anwendung ab. – Eine mögliche Lösung dieses Problems mit Hilfe sogenannter Inklusionsfunktionen im Rahmen der Generativen Modellierung wird im Abschnitt 3.1 dargestellt.
Während sich Effekte wie Rauch oder Nebel auch im approximativen Rendern verwenden
lassen, interessiert man sich beim volume rendering für die transparente Darstellung räumlicher
Dichtefunktionen. Beispielsweise gibt es medizinische Datensätze, die ein dichtes räumliches Gitter von Gewebedichtewerten enthalten, die durch Computertomographie bestimmt wurden. Stellt
man sich das Gewebe transparent vor, wobei zunehmende Dichte zunehmende Lichtabsorption bedeutet, so kann man durch Integration der Lichtintensität entlang eines Strahles eine Darstellung
mit starker räumlicher Wirkung erzeugen.
Viele natürliche Strukturen besitzen die Eigenschaft der Selbstähnlichkeit. Fractal terrains
sind computergenerierte Landschaften, die durch einen iterativen Prozeß erzeugt werden, der immer feinere Strukturen hervorbringt. L-Systeme sind Grammatiken, also Bildungsvorschriften für
Worte einer abstrakten Sprache. Interpretiert man die Buchstaben dieser Worte geometrisch als
Verzweigungen oder Äste, so kann man, wie Lindenmayer herausgefunden hat, die Morphologie
vieler Pflanzen simulieren.
Ein weiteres Gebiet, wo die Darstellung von Oberflächen und Dichtefunktionen eine große
Rolle spielt, ist die ’scientific visualization’. Numeriker benutzen aufwendige Verfahren, um Strömungen von Flüssigkeiten oder Gasen zu berechnen, Minimalflächen zu bestimmen oder herauszufinden, wo technische Bauteile den größten Belastungen ausgesetzt sind. Man benutzt dabei zum
Beispiel die Finite Elemente Methode, um die zugrundeliegenden Differentialgleichungen approximativ zu lösen. Das Ergebnis einer solchen Rechnung, einer Simulation oder einer Optimierung,
zu visualisieren, kann schwierig sein, weil zumeist in hochdimensionalen Räumen gerechnet wird,
sodaß man auf relevante Größen projizieren muß. Bereits die Darstellung einer komplexen Funktion f : C ! C stellt hohe Anforderungen an das Vorstellungsvermögen, wenn man etwa Realund Imaginärteil getrennt als Höhenfelder auf einem Ausschnitt der komplexen Ebene darstellt
oder in Farbkanälen kodiert. Ist das Rechenergebnis ein räumliches Vektorfeld, beispielsweise die
Fließgeschwindigkeit in einem simulierten Wirbel, so ist man gezwungen, Hilfskonzepte zur Darstellung zu benutzen, sei es eine Partikelanimation, sei es, daß man zeigt, wie sich ein Ausschnitt
der Flüssigkeit in der Strömung deformiert, oder man benutzt volume rendering wie in manchen
Wettersimulationen.
In der scientific visualization bedient man sich also eigentlich der vorher vorgestellten Darstellungselemente, und das Finden einer geeigneten Darstellungsmethode ist selbst zu einem eigenen
Forschungsgebiet geworden. Damit wird klar, in welchem Maße eine flexible Handhabung der
konventionellen Darstellungselemente vonnöten ist, um Anwendern eine leistungsfähige Schnittstelle zur Darstellung ihrer räumlichen Modelle anzubieten.
2.3. PRÄZISIERUNG DES OBERFLÄCHENBEGRIFFS
2.3
13
Präzisierung des Oberflächenbegriffs
Die genannten Konzepte der Modellierung waren in vielen Fällen die Antwort auf konkrete Probleme und sind ursprünglich anwendungsgetrieben entwickelt worden. Will man die Ausdruckskraft
von Modellern mit ihren zugrundeliegenden Ansätzen klassifizieren, ist es aber nötig, von diesen
Anwendungen ein wenig zu abstrahieren.
Grundlegende Konzepte der Modellierung und Konstruktion sind zunächst einmal Kurven und
Flächen. Wie bereits angesprochen, kann man diese in impliziter und in parametrischer Form darstellen. Wegen der Schwierigkeiten bei dem Umgang mit impliziten Darstellungen, wo die Auswertung auf Iterationsverfahren beruht, deren Implementierung aufwendig und die im Vergleich
zur direkten parametrischen Auswertung wenig effizient sind, wurden parametrische Kurven und
Flächen das bevorzugte Darstellungselement in der Modellierung. Modellierung mit Metaballs
und Berechnung des Schnitts von Strahl und Fläche aber sind einerseits unverzichtbare implizite
Konzepte, andererseits ist es wie erwähnt nicht zweckmäßig, alle Probleme, die sich als implizite Probleme stellen lassen, mit einer einzelnen allgemeinen Lösungsmethode anzugehen. Dies
hängt auch mit der Dimensionalität der Lösungsmenge der Gleichungen zusammen, die ganz verschieden sein kann von der Dimension der Lösungsmenge als Teilmenge des R3 . Der erwähnte Schnitt zweier parametrischer Flächen hat eine vierdimensionale Lösungsmenge, während die
Schnittmenge als Raumkurve eine eindimensionale Teilmenge des Dreidimensionalen ist.
Weiter ist für die Standardkugel etwa der Wechsel zwischen den Darstellungen sehr einfach,
somit kann man sich für das Sampling der expliziten und für den Strahlschnitt der impliziten Formulierung bedienen und kommt ohne jedes Iterieren aus. Sampling einer impliziten Isofläche per
marching cube dagegen geht von einem ganz anderen algorithmischen Ansatz aus, es sind viele
Nullstellenprobleme zu lösen, und Annäherung einer Flächenschnittkurve durch Kurveninterpolation ist ein weiteres Konzept, das in anderem Zusammenhang sinnvoll sein kann.
Um dieses Problem auszuklammern, soll die Klasse der Objekte, auf denen die Modellierung
im mathematischen Sinne als solche operiert, auf sehr allgemeine Weise definiert werden. Die
einzige Forderung, die man in diesem Zusammenhang an die Oberfläche eines Gegenstands stellt,
ist, daß sie in Teile zerlegt werden kann, und daß es eine parametrische Darstellung eines solchen
Teils gibt, selbst wenn man sie im konkreten Fall nicht angeben oder effizient finden kann.
Definition:
Oberfläche eines Gegenstands als Mannigfaltigkeit
Die Oberfläche eines Gegenstands ist eine kompakte orientierbare geschlossene
zweidimensionale Fläche in R3 . Das bedeutet:
Um jeden Punkt der Fläche läßt sich eine stetige bijektive Abbildung (Karte)
von der Fläche nach R2 angeben
Die Umkehrung einer Karte ist eine Parametrisierung der Fläche
Im allgemeinen läßt sich eine solche Abbildung nicht auf die ganze Oberfläche
fortsetzen
Eine geschlossene Oberfläche hat keinen Rand, das heißt, um jeden Punkt der
Fläche gibt es eine Karte mit offenem Definitionsbereich
Die Fläche ist orientierbar, es gibt also ein Inneres und ein Äußeres der Fläche
Die Oberfläche ist endlich und umschließt damit ein endliches Volumen
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
14
Man fordert von der Oberfläche eines Gegenstandes also nicht, daß sie als Ganze parametrisierbar sein muß. Das hat zum einen den Grund, daß man auch an Gegenständen höheren Geschlechts interessiert ist, die beliebig viele Löcher enthalten. Der Torus ist ein Beispiel für einen
Gegenstand des Geschlechts eins, während die Kugel Geschlecht null hat, und Gegenstände mit
höherem Geschlecht als eins lassen sich nicht durch eine einzelne Parametrisierung erfassen.
Zum anderen kann eine Oberfläche nach dieser Charakterisierung durchaus Stellen enthalten,
wo sie nicht differenzierbar ist, wo man also keine eindeutige Tangentialebene angeben kann,
wie an den Kanten oder Ecken eines Würfels. Doch beschränkt man diese Stellen in der Regel
auf einzelne Punkte oder auf Kurven, also Knicke innerhalb der Oberfläche. Dann kann man die
Oberfläche entsprechend in einen Verbund von Einzelflächen zerlegen, die jeweils in ihrem Innern
differenzierbar sind. Daher ergänzt man die Definition einer Oberfläche auf folgende Weise.
Definition:
Oberfläche eines Gegenstands als Patchkomplex
Die Oberfläche eines Gegenstands kann in einzelne Patches unterteilt werden. Ein parametrisches Patch ist eine kompakte orientierbare reguläre Fläche mit abgeschlossenem Rand. Das bedeutet, daß sich das Patch nicht selbst schneidet und daß es an
allen Stellen eine eindeutige Tangentialebene gibt. Benachbarte Patches sind entlang
ihres Randes verklebt. Das heißt, daß der Rand eines Patches gleichzeitig der Rand
des Nachbarpatches ist, zwischen Patches gibt es also einen mindestens GC 0 -stetigen
Übergang. Stellen, wo mehr als zwei Patches zusammentreffen, sind Knoten des Patchkomplexes. Randstücke zwischen zwei Knoten nennt man Kanten des Patchkomplexes.
Statt von Knoten spricht man manchmal auch von Ecken. – Die Regularität einer Fläche ist
ein differentialgeometrisches Konzept, das die “Glattheit” eines Flächenstückes sicherstellt. Sie
wird zum Beispiel in [do 93] folgendermaßen definiert.
Definition:
Reguläre Fläche
Eine Menge S R3 ist eine reguläre Fläche, wenn es zu jedem Punkt p 2 S eine
Umgebung V R3 gibt und eine Abbildung f : R2 U ,! V \ S R3 , U offene
Menge, für die gilt, daß f ein differenzierbarer Homöomorphismus ist, das heißt eine
bijektive differenzierbare Abbildung mit stetiger Inverser, und daß es an jeder Stelle
p 2 S eine eindeutige Tangentialebene gibt.
Ein Patchkomplex besitzt damit eine topologische Struktur, die man isoliert von der geometrischen Gestalt betrachten kann. Dies ist die Struktur eines eingebetteten Graphen [Prö92], der
vom gleichen Geschlecht ist wie die Oberfläche eines Gegenstands. In einem eingebetteten Graphen gibt es neben Kanten und Ecken auch noch Flächen, die von den Kanten begrenzt werden, in
diesem Fall gerade die einzelnen Patches. Diese grundsätzliche Trennung zwischen geometrischer
und topologischer Struktur wird in [BFH95] vorgestellt, eine detaillierte Ausarbeitung im Rahmen
einer Diplomarbeit findet man in [Ben97].
2.3. PRÄZISIERUNG DES OBERFLÄCHENBEGRIFFS
Definition:
15
Topologische Struktur eines Patchkomplexes
Ein eingebetteter Graph besteht aus Knoten, Kanten und Flächen. Jede Kante
verbindet zwei Knoten und berandet genau zwei Flächen. Es kann jedem Flächenrand ein konsistenter Umlaufsinn (Orientierung) in der Weise zugeordnet werden,
daß jede Kante in beiden Richtungen durchlaufen wird. Weiter ist die Umgebung
jeder Ecke topologisch betrachtet planar, es gibt keine Stellen, wo der Körper auf
einen Punkt eingeschnürt wird. Für jeden Knoten gilt also: Die mit ihm inzidierenden Kanten können in einer zyklischen Liste so angeordnet werden, daß für je zwei
aufeinanderfolgende Kanten a, b gilt: (a; b) ist Teil eines Flächenrandes.
Ein für die Computergrafik wichtiger Spezialfall einer Oberfläche ist dabei der geschlossene
Polygonkomplex, das Polyeder. Dabei sind alle Patches planare Polygone, die nicht konvex sein
müssen und Löcher haben dürfen, an die sich andere Teile des Gegenstands anschließen. Polyeder
haben in der Geschichte der Geometrie eine große Bedeutung und sind im Rahmen der Informatik
Gegenstand der ’computational geometry’. Ein Modell für diese Struktur ist der BRep.
Definition:
BRep
Ein BRep (’boundary representation’) ist ein polyedrischer Gegenstand. Seine Seitenflächen sind zweidimensionale Polygone, abgeschlossene planare Teilmengen
des R3 . Der Rand einer Seitenfläche ist eine geschlossene Streckenfolge. Das Polyeder ist durch die Angabe der Lage der Knoten und der Verbindungsinformationen
von Knoten, Kanten und Flächen eindeutig definiert.
Ein Spezialfall eines Polyeders ist der Dreieckskomplex, wo alle begrenzenden Polygone
Dreiecke sind, wodurch die Bedingung der Planarität automatisch erfüllt ist. — BReps werden
mit Hilfe von Euler-Operatoren konstruiert. Diese benutzt man, um Ecken durch Kantenzüge
zu verbinden, die dann Flächen beranden, und es können Flächen verklebt werden, etwa um aus
einem langgestreckten Quader einen Ring zu machen, indem man die Abschlußflächen identifiziert. Zu jeder Euleroperation gibt es ebenso eine inverse Euleroperation, sodaß man von einer
Operationenalgebra sprechen kann [Män88]. — In vielen Fällen kann es zweckmäßig sein, nichtgeschlossene Körper zu modellieren, ein einzelnes Patch oder einen Verband mehrerer Patches,
der nicht geschlossen ist, aber vielleicht als Teil einer Form an verschiedenen Stellen eines Gegenstands eingesetzt werden soll. Solche nichtgeschlossenen Oberflächen haben dann einen oder
mehrere Ränder, die als Schnittstellen für die spätere Verklebung mit anderen Teiloberflächen dienen. Eine solche Teilform kann man aber ebenfalls als geschlossenen Patchkomplex modellieren,
wenn man die Ränder mit Flächen schließt, die nur konzeptionell, das heißt topologisch, vorhanden sind, aber nicht angezeigt werden und keine Parametrisierung besitzen. Solchen Flächen fehlt
also vorerst die Geometrie, die durch ein späteres Verkleben nachgereicht wird.
Definition: Hollow-Fläche
Eine Hollow-Fläche ist ein Patch eines Patchkomplexes, das topologisch vorhanden
ist, aber nicht parametrisiert werden kann. Hollows werden von allen geometrischen
Algorithmen ignoriert und nicht gerendert. Der Rand eines Hollows ist eine geschlossene Raumkurve, die einzelnen Randstücke werden durch die Ränder der umgebenden Patches definiert. Zwei Patchkomplexe können miteinander verklebt werden, indem die Ränder zweier gegeneinander orientierter Hollows miteinander identifiziert
werden. Dabei werden die Hollows beider Oberflächen im topologischen Sinne mit
Hilfe von Euler-Operationen entfernt und ein zusammenhängender Graph erzeugt.
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
16
So kann ein einzelnes Patch, ein einzelnes Dreieck, bereits als geschlossener Körper angesehen
werden, indem man auf seiner Rückseite ein Hollow als weiteres Patch annimmt. Nach obiger
Definition ist es ausgeschlossen, daß ein Hollow zu weiteren Hollows benachbart ist, denn der
Rand eines Hollows muß tatsächlich geometrisch vorhanden sein. Somit dienen Hollows gerade
als Anschlußstellen für das Zusammensetzen von Gegenständen.
Bei Patchkomplexen mit Hollow-Flächen kann man natürlich nicht mehr von einem umschlossenen Volumen sprechen.
Modellierung ist die Modellierung einzelner Patches und das Verkleben zu einem geschlossenen Verbund, der die Oberfläche eines Gegenstands definiert.
Diese Sichtweise der Modellierung ist motiviert vom mathematischen Begriff der Mannigfaltigkeit. Aus dieser Perspektive ist die planare Approximation nur ein spezieller Verband von Freiformflächen, dies gestattet eine vereinheitlichte Betrachtungsweise, wo nicht mehr unterschieden
wird zwischen geometrischen Grundkörpern, die als Ganzes gegeben sind, zwischen verschiedenen Repräsentationen von Freiformflächen und zwischen großen Dreiecksverbänden, weil die
gleiche topologische Struktur zugrundeliegt.
Um diesen Ansatz in einem Modeller zu realisieren, muß daher eine einheitliche Datenstruktur
für die verschiedenen Typen von Oberflächen und zusammengesetzten Oberflächen implementiert
werden, was sehr aufwendig sein kann. Zudem können Effizienzaspekte eine Rolle spielen, denn
wenn in einer Anwendung nur eine bestimmte Flächenklasse benötigt wird, kann es sinnvoll sein,
Overheads zu vermeiden, die durch die Spezialisierung einer sehr allgemein angelegten Datenstruktur erzeugt werden. Andererseits können Speziallösungen sehr aufwendig zu erweitern sein,
wenn sich die Notwendigkeit einmal ergibt. Es gibt also keine Patentlösung für das zu verwendende Modell, doch der im nächsten Abschnitt erläuterten historischen Entwicklung von Modellierparadigmen liegt eine gewisser Trend hin zu größerer Allgemeinheit zugrunde.
Identifiziert man Gegenstände mit geschlossenen Patchkomplexen, so erhält man die erwähnte
hierarchische Gliederung von Punkten, Kurven und Flächen. Der Produktionsprozeß geht dagegen
zumeist in der umgekehrten Richtung vor sich, indem zunächst Kurven gezeichnet werden, diese
Kurven dienen zur Definition von Teilflächen mit den entsprechenden Randkurven, die verschiedene Flächenelemente werden entlang ihrer Randkurven verklebt oder verblendet. Patchkomplexe
sind in sofern das allgemeinste Konzept, weil bereits ein einzelner Kurvenzug topologisch gesehen
einen Patchkomplex darstellen kann, als zwei Knoten, eine Kante und eine Hollow-Fläche, die den
Kurvenzug als geschlossene Randkurve besitzt. Eine einzelne Fläche ist ebenfalls, wie erwähnt,
ein Patchkomplex, wenn man den Rand als Rand eines Hollows betrachtet. Die Kombination und
Erweiterung zu der geschlossenen Oberfläche eines Gegenstands, sei es eine planare Approximation oder ein Komplex von Freiformflächen, stellt sich somit in einem einheitlichen Zusammenhang
dar.
2.4
Konventionelle Modellieransätze
Ausgangspunkt der Untersuchung war die Frage, wie Modellieransätze zu klassifizieren sind. In
dreißig Jahren Entwicklung der computerunterstützten Modellierung sind viele Modellieransätze
und -verfahren entstanden, die zunächst nur ad hoc zur Lösung bestimmter Anwendungsprobleme
entworfen worden sind. Dabei war oft klar, wie die Lösung für bestimmte Probleme auszusehen
hat. – Heute hat die Modellierung aber eine Diversifizierung erfahren, die von rein synthetischen
kurzen Werbefilmen über aufwendig in Filmaufnahmen integrierte Animationssequenzen bis zur
Konstruktion kompletter Produkte in der computerunterstützten Fertigung reicht. Im CAD müssen
2.4. KONVENTIONELLE MODELLIERANSÄTZE
17
in technischen Bereichen Modelle hoher Genauigkeit erzeugt werden, wobei besondere Ansprüche
an die Beherrschbarkeit der Modelle und die Kontrollierbarkeit der Form im Detail gestellt werden,
während bei synthetischen Filmen ästhetische Gesichtspunkte im Vordergrund stehen und eine
Anpassung der Bedienung von Modellern an die künstlerische Denkweise wichtig ist.
Es sind dabei zwei Probleme stark in den Vordergrund getreten, die unabhängig vom Anwendungsgebiet eine Rolle spielen und die die Entwicklung neuer Modeller vorangetrieben haben.
Änderbarkeit
Ein Modellierkonzept muß die Möglichkeit offenhalten, bei Änderung von Designvorgaben
ein Modell in jeder notwendigen Weise nachträglich zu verändern.
Wiederverwendbarkeit
Einmal entwickelte Lösungen für Modellierprobleme müssen auch an anderen Stellen wieder verwendet werden können, damit eine nur leicht variierte Aufgabe nicht ganz von neuem
gelöst werden muß.
Zum einen stellt sich nun die prinzipielle Frage, ob es überhaupt einen Modellieransatz geben
kann, der die ganze Bandbreite der Anwendungen mit ihren spezifischen Erfordernissen abdecken
kann und gleichzeitig diese Bedingungen erfüllt, andererseits ist man im akademischen Bereich
natürlich an der Frage interessiert, ob es eine Möglichkeit gibt, all diese Ansätze zu formalisieren
und quantitativ oder qualitativ zu vergleichen. Dies ist auf der Basis von Beschreibungssprachen
für Modelle eher möglich, als wenn man versucht, die Leistungsfähigkeit interaktiver Programme
zu messen. Andererseits bedeutet Modellierung nicht nur, eine kompakte, verständliche textuelle
Beschreibung von Modellen zu erreichen, vielmehr ist Interaktivität ein integraler Bestandteil der
Arbeit, virtuelle Räume zu entwerfen.
Ein besonderes Problem, das die praktische Relevanz eines Modellieransatzes entscheidend
bestimmt, ist die Frage, wie effizient sich modellieren läßt. Die Modelliereffizienz eines Modellers läßt sich einerseits ganz klar über den zeitlichen und finanziellen Aufwand bestimmen,
der nötig ist, um eine bestimmte Modellieraufgabe zu erfüllen, andererseits kommen dabei aber
auch schwer meßbare Größen wie Einarbeitungszeit und Beherrschbarkeit eines Modelliersystems
hinzu. Zudem ist wegen der besprochenen Komplexität der Systeme und der Aufgaben zu beobachten, daß sich Anwender ein Repertoire von Vorgehensweisen zulegen, die sie einsetzen, um
die konzeptionellen Schwächen eines Modelliersystems zu umgehen. So werden die im folgenden
besprochenen Hilfskonzepte durchaus als Stärken denn als konzeptionelle Schwächen gesehen, es
stellt sich natürlich die Frage, ob Alternativen zum ’state of the art’ der Modellierung bestehen,
die effizienteres Arbeiten möglich machen.
Konventionelle Modellieransätze können grob in die Kategorien approximationsbasiert, objektorientiert und parametrisch eingeordnet werden. Die historische Entwicklung von Modellern
kann insgesamt als eine Entwicklung zu Ansätzen gesehen werden, die der Aufgabenstellung immer besser angpaßt sind. Das bedeutet, man versucht, eine möglichst natürliche Sichtweise der
Modellierung zu entwickeln.
2.4.1
Approximationsbasiertes Modellieren
Approximationsbasierte Modeller zeichnen sich dadurch aus, daß sämtliche beschriebenen Konzepte, die beim Modellieren benutzt werden, intern auf Polygonkomplexen basieren. Körper werden nicht in ihrer abstrakten Form als Kugeln oder Zylinder verarbeitet, sondern eine Szene enthält
ausschließlich BReps und Streckenzüge, die die Form von Flächen und Kurven bis zu einer vorgegebenen Genauigkeit annähern. Dies hat den Vorteil, daß es eine einheitliche Datenstruktur gibt,
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
18
auf der alle Algorithmen intern operieren. Der Modeller bietet Operationen allein für die Manipulation von BReps an, und all diese Operationen produzieren wieder BReps. Die computational
geometry bietet einen reiche Fundus von solchen Algorithmen, und für viele Hilfsoperationen auf
Polygonen und Polyedern gibt es wohlfundierte Lösungen citeMehlhorn84, [SDK96].
Punktlokalisation, ’point in polygon’- und ’point in polyeder’-Tests
Schnitte von Strecken mit Polygonen, von Polygonen und von Polyedern
Algorithmen auf konvexen Polygonen, Bildung konvexer Hüllen
Schnitte von Halbräumen, lineare Programmierung
Polygonales Clipping, Sichtbarkeitsbestimmung
Datenstrukturen für Raumunterteilungen
Distanzbestimmung, Kollisionsdetektion
Triangulierung, Tetraedierung, Sweep-Paradigma
Aus diesen Routinen kann man effiziente Algorithmen für höhere Operationen zusammensetzen, man kann Deformationen, Boole’sche Operationen zwischen Primitivobjekten und lokale
Manipulationen allein auf BRep-Basis beschreiben. Für bestimmte Problemklassen bietet die Modellierung auf BRep-Basis also sehr effiziente Lösungsmöglichkeiten an. Ein Beispiel ist das CSG,
das im allgemeinen für Freiformflächen als implizites Problem wie erläutert sehr schwer zu lösen
ist. CSG auf BReps dagegen ist ein diskretes Problem, und die Klasse der polyedrischen Körper ist unter Schnittbildung abgeschlossen, im Gegensatz etwa zu der Klasse der von bikubischen
Bézier-Patches begrenzten Gegenstände. CSG ist damit ein Beispiel für eine Problemklasse, die
einzig auf Basis von Approximationen zufriedenstellende, wenn auch schwer zu implementierende Lösungen besitzt.
Der approximationsbasierte Ansatz besitzt jedoch auch gravierende Nachteile.
Genauigkeitsprobleme
Da die ursprüngliche Form einer Kugel, eines Kegels, einer Freiformfläche nicht mehr verfügbar ist, kann an einzelnen Stellen der Approximation nicht mehr nachträglich die Genauigkeit erhöht werden, wenn sich herausstellen sollte, daß dies nötig wird.
Performanceprobleme
In vielen Anwendungen werden Toleranzgrenzen vorgegeben, die ein Objekt einhalten muß.
Um einen Kegel, eine Kugel etc. sehr genau anzunähern, benötigt man unvernünftig große
Netze, die Probleme bei Rechenzeit und Speicherplatz verursachen.
Niedriger Abstraktionsgrad
Das Hauptproblem liegt damit also schon im Ansatz begründet. Es ist nicht vernünftig, sich
auf die Darstellung von Polyedern zu beschränken, wenn man eigentlich gar keine Polyeder
meint, sondern deformierte Quader und Kugeln, die miteinander verschnitten werden sollen
und nachher wieder deformiert werden. - Das bedeutet, das Problem besteht im niedrigen
Abstraktionsgrad des Modellierparadigmas.
Das Problem, die richtige Sichtweise für ein Problem zu finden, läßt sich auch auf folgende
Weise formulieren: Es gilt, die richtigen Freiheitsgrade zu isolieren. Für approximationsbasierte
Modeller bedeutet dies, daß es keinen Sinn macht, eine Kugel, die durch Mittelpunkt und Radius eindeutig definiert ist, durch ein Dreiecksnetz von Tausenden von Punkten und Abertausenden
von Freiheitsgraden zu modellieren. - Die Entwicklungsgeschichte der Modellierung läßt sich also nachträglich als Suche nach der richtigen Sichtweise interpretieren, in der immer komplexere
Modelle neue Anforderung an die Handhabbarkeit, mithin die Isolierbarkeit der richtigen Freiheitsgrade gestellt haben.
2.4. KONVENTIONELLE MODELLIERANSÄTZE
2.4.2
19
Objektorientiertes Modellieren
Konventionelle interaktive Modellierung der heute kommerziell dominierenden Systeme geht von
einem Objektansatz aus. Die Gegenstände sind dabei durch eine Reihe von Eigenschaftsfeldern
definiert, deren Inhalt interaktiv bestimmt wird. Ein gegebenes Objekt wird durch Parameteränderung solange modifiziert, bis es den Vorgaben des Modellierers entspricht. Die dabei modellierten
Gegenstände sind Objekte im Sinne der objektorientierten Programmierung (OOP). Das bedeutet,
das Grundobjekt Quader enthält beispielsweise als Informationen Länge, Breite und Höhe. Der
Objektansatz kann auch dahingehend erweitert werden, daß nicht nur Gegenstände durch Objekte
repräsentiert werden, sondern auch Operationen wie Transformationen oder andere Modifikationen an Gegenständen. Position und Orientierung im Raum werden über Transformationsobjekte
bestimmt, Deformationen wie Verdrehungen etwa besitzen dann als Objektinformationen zum
Beispiel Drehachse und Drehwinkel pro Achsenabschnitt. So können alle vorgenannten Konzepte, die in einem Modeller benutzt werden, als Instanzen von Objektklassen formuliert werden, die
in einer Klassenhierarchie angeordnet sind. Ein ausgewähltes Objekt stellt sich nach außen als ein
Satz von Parametern dar, die interaktiv bestimmt werden können. Einerseits gibt es dazu die Technik der Dialogboxen, in die per Tastatur Werte eingetragen werden können, andererseits benutzt
man Gizmos oder Handles, das sind Hilfsobjekte, die im Dreidimensionalen dargestellt werden
und den Einfluß von Parameteränderungen unmittelbar zeigen sollen.
Der Aufruf von Grundobjekten und Operationen geschieht dabei entweder durch Auswahlmenüs oder durch Icons, die auf einer Werkzeugleiste angeordnet sind. Man spricht bei Operationen, die auf Kurven, Flächen oder Gegenständen operieren, daher auch von Werkzeugen.
Objektorientierte Modeller besitzen damit ein Modell, das es gestattet, die abstrakte Darstellung eines Gegenstands beizubehalten. Intern kann ein solcher Modeller jedoch ebenfalls approximationsbasiert arbeiten, entscheidend ist aber, daß die BReps in der Szene eine Verbindung zum
Objekt behalten, sodaß etwa die Genauigkeit einer Approximation nachträglich noch verändert
werden kann.
Ein solches System kann man daher als rein objektorientierten Modeller beschreiben, wo
es mit Ausnahme eines Szenengraphen keine “höheren” oder Metaobjekte gibt, die benutzt werden können, um Verbindungen zwischen Objekten zu modellieren oder Mengen von Objekten zu
bilden. In einem rein objektorientierten Modeller hat man daher nur Zugriff auf die gegenwärtig
definierten Objekte. Daher unterliegt man bei diesem Ansatz hauptsächlich den Beschränkungen,
daß die Konstruktionsgeschichte eines fertig modellierten Gegenstands nicht mehr verfügbar ist,
und daß funktionale Abhängigkeiten zwischen den Parametern verschiedener Objekte nicht expliziert werden können.
2.4.2.1
Verlust der Konstruktionsgeschichte
Die erste Einschränkung bedeutet dabei, daß das Ziel der objektorientierte Modellierung allein
das modellierte Objekt ist. Es wird eine Szene definiert, indem Objekte definiert werden. Primitive wie Kreise, Linien, Polygonzüge oder Kurven im Zweidimensionalen, bzw. Kugeln, Quader
und Freiformflächen im Dreidimensionalen werden als Objekte verstanden. Diese Objekte haben
als Instanzen von Objektklassen bestimmte Eigenschaftsfelder, Slots, die gefüllt werden müssen,
um ein Objekt zu definieren. Diese Felder enthalten dabei Daten wie 3D-Vektoren für Raumpositionen oder Skalare, um Winkel oder Längen zu spezifizieren. Sind alle Eigenschaften aller
Objekte festgelegt, ist damit die Szene definiert und kann gerendert werden.
Der Konstruktionsprozeß ist bei zunehmender Komplexität der Objekte aber mehrstufig. Das
bedeutet, daß Objekte kombiniert werden, um aus ihnen neue Objekte zu erzeugen. Beispiele sind
das erwähnte CSG, oder die Konstruktion einer Blending-Fläche aus zwei Raumkurven, zwischen
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
20
denen linear interpoliert wird. Ist die Konstruktion abgeschlossen, so verfügt der Modellierer zwar
über den Gegenstand, den er für seine Zwecke braucht, aber der Konstruktionsprozeß ist abgeschlossen und kann nicht nachträglich mit variierten Parametern für die bei der Konstruktion benutzten Objekte wiederholt werden. Somit ist jedes modellierte Objekt ein Unikat in dem Sinne,
daß es zwar oft beliebig kopiert oder weiter verändert werden kann, der Modellierprozeß selbst
aber ist beendet. Dies gilt insbesondere für interaktive Eingaben durch Mausbewegungen, mit denen Objekte auf zunächst bequeme Weise durch die erwähnten Handles manipuliert werden können. Diese Bewegungen sind aber nicht auf identische Weise zu wiederholen oder gar nachträglich
abzuändern, es kann nur iterativ immer weiter verändert werden.
2.4.2.2
Verlust funktionaler Abhängigkeiten
Die zweite Einschränkung, der Verlust funktionaler Abhängigkeiten, bezieht sich auf einen tiefliegenderen Mangel objektbasierter Modeller. Denn auch interaktive Eingaben, die im Verlauf
des Modellierprozesses getätigt werden, sind nicht allein von gestalterischen Gesichtspunkten bestimmt. Insbesondere bei der computergestützten Konstruktion müssen geometrische Konstruktionen vielen Bedingungen und Vorgaben genügen. Ein einmal erstelltes Modell entspricht diesen
dann zwar, aber bereits kleine Änderungen der Vorgaben haben unter Umständen ein Wiederholen
vieler Arbeitsgänge zur Folge.
Der Grund für diese Schwäche ist, daß im objektorientierten Paradigma die Slots der Objekte
in der Szene zwar Werte enthalten, aber in der Regel keinen Bezug zu den Berechnungen, die diese
Werte liefern. Es gibt viele Fälle, wo dies eine Rolle spielt, insbesondere in CAD-Programmen und
bei der 2D-gestützten Konstruktion.
Bestimmte Maße beziehen sich aufeinander, wie etwa bei Bohrungen oder bei Verbindungen
von Teilen
Andere Werte sind aus Berechnungen hervorgegangen, wie etwa ein Polygon, das in einen
Kreis eingepaßt wird, Gegenstände, die einander berühren, oder bei der Ausrichtung von
Gegenständen in bestimmter Weise
Bei Animationen ist die mangelnde Dynamik der Abhängigkeitsbeziehungen besonders augenfällig, wenn Gegenstände, deren Bewegung einen Bezug aufeinander hat, mit getrennten
Bewegungspfaden modelliert werden müssen
Um diesen Schwächen zu begegnen, verfügen CAD-Programme und Modeller über diverse
Hilfmechanismen. Insbesondere das Modelliersystem AutoCAD der Firma Autodesk [Aut96c] ist
ein quasi-Standard zum 2D-gestützten Konstruieren und bietet eine Palette von Werkzeugen an,
die den Zeichengewohnheiten von Ingenieuren und Architekten entgegenkommen.
Four Views-Technik
Von Modellern werden meist vier Sichten angeboten, eine perspektivische, sowie Draufsicht, Sicht von links und von vorn. Konstruiert man in diesen Sichten, ist bereits die Orthogonalität vieler Konstruktionselemente gesichert. Es ist sehr einfach, mit dieser Technik
rechteckige Räume zu konstruieren und diese über-, neben- oder hintereinander zu plazieren. Aufwendige Freiformkonstruktionen wie Teile von Automobilen oder Flugzeugen dagegen profitieren deutlich weniger von dieser Konstruktionsweise. Es bleibt zu spekulieren,
ob diese rechteckige Sicht der Dinge eine natürliche ist und den Zwecken der Konstruktion angepaßt wurde, oder ob es dadurch einfach vorwiegend zu rechteckig konstruierten
Objekten kommt.
2.4. KONVENTIONELLE MODELLIERANSÄTZE
21
Fang
Ist etwa in AutoCAD ein Punkt einzugeben, kann der Benutzer über sogenannte Fangmodi spezifizieren, ob dieser zum Beispiel durch Schnitt zweier Linien, durch Fällen eines
Lotes oder als Berührungspunkt der Tangente an einen Kreis gegeben sein soll. AutoCAD
bietet dem Modellierer aber überhaupt keinen Rückgriff auf die Konstruktionsgeschichte.
Veränderungen können daher extrem aufwendig werden, weil die gefangenen Punkte bei
veränderten Modellparametern jedesmal neu gefangen werden müssen.
Gitter
Eine andere Hilfe ist das Gitter, wo nicht der per Maus eingegebene Punkt direkt verwendet
wird, sondern der auf einem Hilfsgitter am nächsten liegende. Dies bedeutet, man schränkt
die zur Auswahl stehenden Punkte ein, um so Bezug auf bestimmte, in einer bestehenden
Konstruktion möglicherweise bereits verwendete Punkte nehmen zu können. Dies funktioniert allerdings nur eingeschränkt, Kreise etwa haben in der Regel bloß vier Punkte, die
genau auf dem Gitter liegen.
Lokale Hilfskoordinatensysteme
Um die Beschränkung auf xy-, yz und zx-Ebene als Konstruktionsflächen aufzuheben, können Konstruktionen in bezug auf ein im Raum liegendes lokales Koordinatensystem vorgenommen werden. Diese Technik ist ein Vorläufer der Szenenhierarchie, denn lokal konstruierte Objekte werden intern ins Weltkoordinatensystem eingebettet, sodaß nachträgliche
Änderungen des lokalen Koordinatensystems keinen Einfluß mehr auf die Konstruktion haben.
Modellieren mit constraints
Um Abhängigkeiten zwischen Konstruktionselementen zu spezifizieren, gibt es die Technik des constraint-basierten Modellierens, wo explizite Bedingungen formuliert werden, die
ein Modell zu erfüllen hat. So kann man fordern, daß Elemente einander berühren sollen,
daß bestimmte Winkel einzuhalten sind, oder wo sich Geraden schneiden sollen. Dies stellt
den Benutzer vor die schwierige Aufgabe, nicht zuwenige und nicht zuviele Bedingungen
vorzugeben, anderenfalls ist das zu lösende Gleichungssystem unter- oder überbestimmt.
AutoCAD etwa besitzt ein entsprechendes Erweiterungsmodul, die constraint-Technik ist
somit durchaus von praktischer Relevanz. Problematisch dabei ist die große Anzahl denkbarer Nebenbedingungen, die nicht alle vom System angeboten werden können, und die
Erweiterung dieser Idee auf dreidimensionale Modelle, wo die möglichen constraints entsprechend komplexer werden.
Die revolutionäre Wirkung der Informationsverarbeitung in der Rationalisierung schematischer Arbeitsabläufe liegt darin begründet, daß Computerprogramme Anwendern die Möglichkeit
bieten, Informationen regelgesteuert bearbeiten zu lassen. In der dreidimensionalen Modellierung
ist ein Anwender aber noch immer gezwungen, selber alle Arbeitsschritte zur Definition eines
virtuellen Raumes durchzuführen, und nicht die Konstruktion, sondern das Modell selber zu beschreiben, zu erarbeiten. Daher ist es wichtig, eine Methode zur Beschreibung von Konstruktionen zu finden, nicht nur eine verständliche geometrische Programmiersprache, sondern auch eine
Möglichkeit zur Visualisierung dieser Beschreibung.
2.4.3
Parametrisches Modellieren
Um die erste Beschränkung des rein objektorientierten Ansatzes, den Verlust der Konstruktionsgeschichte eines Objektes, aufzuheben, gibt es eine konsequente Weiterentwicklung des rein objek-
22
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
torientierten Ansatzes, den parametrischen Modeller. Dieser behält mit Hilfe von Metaobjekten
die abstrakte Objektinformation nicht nur für Gegenstände bei, sondern auch für die Operationen,
die auf die Gegenstände angewandt worden sind. Das bedeutet, daß in einem viel weiteren Rahmen auf die Parameter Einfluß genommen werden kann, die während des Konstruktionsprozesses
eingegeben worden sind. Ein Beispiel für die Realisierung dieser Idee ist der Modeller 3D-Studio
Max der Firma Kinetix [Aut96b], [Aut96a]. Dort gibt es das Konzept des Modifier Stack, ein
Metaobjekt, das zunächst nur einen Gegenstand enthält und dann sukzessive um die angewandten
Deformationen wie Verjüngen und Biegen mit den jeweils eingegebenen Parametern ergänzt wird.
Damit ist die Konstruktionsgeschichte eines Objektes also zugänglich, und die interaktiv eingegebenen Parameter von Deformationen oder Transformationen können nachträglich nach Bedarf
verändert werden, während die Reihenfolge der Anwendung erhalten bleibt und die Auswirkung
von Parameteränderungen auf das fertige Objekt direkt im Dreidimensionalen demonstriert werden kann.
Ein zusätzlicher Vorteil dieser Idee ist, daß sämtliche Parameter der Objekte, wie zum Beispiel
die Stärke eines räumlichen Turbulenzfeldes, mit Hilfe der Keyframe-Technik animiert werden
können. Dabei werden auf einer Zeitleiste verschiedene Zeitpunkte eines Intervalls angewählt.
Der Modellierer trägt die gewünschten Parameter in die zu animierenden Objekte ein, durch diese
Werte wird dann eine kontinuierliche Interpolationskurve gelegt, die zu den den vorgegebenen
Zeitpunkten die vorgegebenen Werte annimmt. Somit kann für jeden Zeitpunkt einer Animation
durch Interpolation eine Szene erzeugt werden, auf diese Weise kann man zeitliche Verläufe in
einem weiten Rahmen modellieren.
Grenzen gibt es für diesen Mechanismus nur dort, wo sich eine Transformation auf bestimmte
Parameter eines Objektes bezieht, weil diese Parameter dann nicht mehr nachträglich verändert
werden können. Beispielsweise gibt es in 3D-Studio Max Werkzeuge zur Manipulation einzelner
Punkte des BReps eines Gegenstands. Danach aber kann die Approximationsgüte natürlich nicht
mehr verändert werden, weil die manipulierten Punkte dann nicht mehr existieren würden. - Zum
anderen wird der Mechanismus des Modifier-Stack in 3D-Studio Max nicht durchgängig angewandt, es gibt eine ganze Reihe von Änderungen, die man an Objekten, an Teilobjekten oder der
Szene bzw. an einer Subszene durchführen kann, die nicht in irgendeiner Form in Metaobjekten
gespeichert werden. Dies stellt aber natürlich weniger eine prinzipielle Schwäche des Ansatzes
dar als vielmehr eine der konkreten Realisierung.
Modifier können sich dabei auch auf Referenzen eines Gegenstands beziehen. Das bedeutet,
daß man zunächst die Grundform eines Gegenstands modellieren kann, um dann verschiedene
Instanzen dieses Gegenstands jeweils unterschiedlich weiterzubearbeiten.
Das Konzept eines Modifier-Stacks funktioniert bei 3D-Studio Max vor allem deswegen so
gut, weil dort im wesentlichen mit globalen Operationen gearbeitet wird, beispielsweise Verzerrungen oder Transformationen, die sich jeweils auf ein gesamtes Objekt beziehen. Ein anderer
Modeller, Rhino der Firma McNeel Associates, ist zunächst nur rein objektorientiert, basiert aber
anders als 3D-Studio Max intern nicht auf der approximativen Darstellung der Gegenstände als
BReps, sondern benutzt als Basis-Datenstruktur NURBS, verwendet also einen Freiform-Ansatz.
Rhino verfolgt dabei eine klare Trennung von Kurven, Flächen und Gegenständen und bietet mannigfache Werkzeuge an, um Kurven zu Flächen zu verblenden, um aus Flächen Kurven zu extrahieren, um aus Flächen und Kurven Gegenstände zu machen, und um aus Gegenständen durch
Schnitte Kurven oder Flächen zu gewinnen. Somit gibt es eine große Anzahl von Werkzeugen, die
auf mehr als einem Objekt als Eingabe operieren. Für einen solchen Modeller macht ein ModifierStack aber wenig Sinn, in diesem Fall müßte man sich einer Baumstruktur von Operationen bedienen.
Man kann jedoch die Ausdrucksfähigkeit eines parametrischen Modellers mit Modifier-Stack
2.5. GENERATIVES MODELLIEREN
23
oder -Baum noch weiter erhöhen, wenn man zuläßt, daß ein Teil eines Stacks oder ein Teilbaum
als eigenständiges Objekt behandelt wird. Besonders bei werkzeugreichen Freiform-Modellern
wie Rhino können Konstruktionen über sehr viele Zwischenschritte erfolgen. Dabei werden unter
Umständen viele Objekte auf sehr ähnliche Weise erzeugt. Es wäre daher hilfreich, wenn man
eine Reihe von Konstruktionsschritten als neues eigenständiges Werkzeug behandeln könnte und
die Teile der Konstruktionen, die jeweils anders sind, als Parameter dieser Werkzeuge zu sehen
wären. Auf diese Weise könnte sich ein Modellierer eine Werkzeugbibliothek anlegen, die auf
seine spezifischen Aufgaben zugeschnitten ist.
Das würde bedeuten, man könnte beispielsweise ein Fenster nicht einfach als Objekt, sondern
als ein Werkzeug modellieren, das lediglich eine Profilkurve erhält sowie Höhe und Breite, und
das Werkzeug konstruiert daraus entsprechend einen profilierten Fensterrahmen mit Gehrungen in
den Ecken durch Rückgriff auf einen Satz von Elementarwerkzeugen des Modellers, oder indem
andere Werkzeuge aus der Bibliothek des Anwenders benutzt werden. Ein entsprechendes Konzept
gibt es jedoch in keinem der kommerziellen Modeller, die bei Konstruktionsaufgaben im CAD
oder bei Filmprojekten eingesetzt werden, es handelt sich daher um eine, wenn auch naheliegende,
so doch hypothetische Möglichkeit zur Erweiterung parametrischer Modeller.
Sieht man von den spezifischen Einschränkungen existierender Modeller ab und führt die Idee
des Modifier-Stacks in dieser Weise konsequent weiter, kann man die Ausdrucksfähigkeit des
parametrischen Modellierparadigmas folgendermaßen charakterisieren.
Modifier-Baum
Alle Schritte der Konstruktion eines Gegenstandes werden protokolliert, zu allen Operationen und ihren Parametern werden entsprechende Objekte, Modifier, in einem baumartig
strukturierten Metaobjekt gespeichert.
Modifier-Baum der Gesamtszene
Dieser Mechanismus wird durchgängig für alle Operationen benutzt, die einen Einfluß auf
die Geometrie der Gesamtszene haben, mithin sind die Modifier-Bäume der einzelnen Gegenstände Zweige des Szenenbaumes.
Modifier-Baum mit eigener Identität
Eine Reihe von Modifikationen kann als solche, unabhängig vom Basisobjekt, als neues
Werkzeug betrachtet und in eine Bibliothek von anwenderdefinierten Werkzeugen eingetragen werden, die genauso benutzt werden können wie die eingebauten Elementarwerkzeuge
des Modellers.
2.5 Generatives Modellieren
Über die parametrische Modellierung läßt sich in der geschilderten Weise die erste der genannten
Beschränkungen des rein objektorientierten Modellieransatzes beheben, den Verlust der Konstruktionsgeschichte. Die zweite Einschränkung jedoch besteht jedoch nach wie vor. Parametrische
Modelle ermöglichen, wenn sie entsprechend realisiert sind, nachträglichen Zugriff auf sämtliche
während der Konstruktion interaktiv spezifizierten Variablen. Diese Parameter aber sind natürlich
nicht völlig willkürlich, sondern es bestehen vielfältige Abhängigkeiten. Gegenstände werden in
virtuellen Räumen plaziert, ihre Position ist also abhängig von den Dimensionen, in denen modelliert wird. Gegenstände liegen auf anderen Gegenständen, diese Abhängigkeit wird mit Hilfe
lokaler Koordinatensysteme in der Szenenhierarchie modelliert. Gegenstände werden in einer Reihe, auf einem Kreisbogen oder in anderen regelmäßigen Strukturen angeordnet, oder es werden
Transformationen angewandt, deren Parameter regelgesteuert variiert werden.
24
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
Konstruktionen dieser Art sind natürlich vor allem wichtig bei industriellen Anwendungen,
wo technische Bauteile genauen Maßvorgaben genügen müssen. Bei Filmproduktionen oder allgemein im kreativen Bereich aber ist es ebenso wichtig, die vielen Parameter der Objekte in einer
Szene zu beherrschen, um mit einer überschaubaren Menge von Eingriffen Änderungen auf einem
hohen Abstraktionsniveau durchführen zu können, die sich auf viele Objekte zugleich auswirken.
Dies ist im Zusammenhang mit der angestrebten Isolierung der richtigen Freiheitsgrade zu sehen,
die ja ein grundsätzliches Problem der Modellierung darstellt.
Weil der constraint-Ansatz wie beschrieben aber wegen der vielen denkbaren Arten von constaints und dem Problem über- bzw. unterbestimmter Systeme wenig praktikabel ist, muß über
einen allgemeineren Ansatz nachgedacht werden.
Interaktive Programmierbarkeit eines Modellers, eine interpretierte Sprache, in der die einzelnen Modellierschritte ausgedrückt werden können, unter Benutzung angepaßter Datenstrukturen, wäre eine mögliche Lösung, um die Ausdruckskraft eines Modellers zu erhöhen. Ein Modeller, der mit einer Programmiersprache Turingmächtigkeit erlangt, stellt das Optimum an Ausdrucksfähigkeit dar, wenn auch möglicherweise nicht an Modelliereffizienz. Um die Ausdrucksfähigkeit nicht auf Kosten der Effizienz zu erhöhen, sollte daher nach einer Lösung gesucht werden, die Konzepte einer Programmiersprache realisiert, ohne zur Eingabe von Programmtexten zu
zwingen.
Dabei mag erstaunen, daß prozedurale Konzepte wie Schleifen oder if-then-else-Verzweigungen im Kontext des Modellierens wenig Sinn machen. Dies liegt darin begründet, daß die Schwierigkeit der Modellierung weniger in einem Mangel an algorithmischer Flexibilität liegt, sondern
mehr in der Beschreibung aufwendiger Datenstrukturen. Als Resultat eines Modellierprozesses
erhält man nicht einen komplizierten Algorithmus, sondern eine Szene, gegeben durch komplexe,
verschachtelte, untereinander verbundene Datenstrukturen. Der Modellierprozeß selber ist iterativ, allgemein werden Objekte in einer Szene schrittweise verändert. In der Modellierung kommen
zudem hochstrukturierte Datenstrukturen zum Einsatz, wobei zwischen den Daten viele funktionale Abhängigkeiten bestehen. Daher sollte geprüft werden, in wiefern sich funktionale Konzepte
ergänzend zum bestehenden objektorientierten Ansatz verwenden lassen.
2.5.1
Funktionale Konzepte in der Modellierung
Der Kern einer Integration funktionaler Konzepte ist es, die statischen Beziehungen in objektorientierten Modellen dadurch dynamischer zu machen, daß die funktionalen Abhängigkeiten expliziert
werden. Slots enthalten somit nicht mehr nur etwa eine Zahl als Parameter, sondern eine Funktion,
die eine Zahl produziert und dafür wiederum bestimmte Eingabeparameter braucht, die entweder
ebenfalls auf irgendeiner Berechnung beruhen, oder die einfach zu Parametern des Modells erklärt werden und erst im weiteren Verlauf des Modellierens spezifische Werte erhalten. Somit
erhält man parametrisierte Konstruktionen.
Dies bedeutet aber gerade, daß Modellierung und funktionale Programmierung in sofern eng
verwandt sind, als in beiden Gebieten die Abbildung das zentrale Konzept ist. Der Modellierer
wendet iterativ Funktion um Funktion, die ihm sein Modeller zur Verfügung stellt, auf die Szene und ihre Elemente an, bis er sein Ergebnis “berechnet” hat; er bildet also Szenen auf Szenen
ab. Andererseits kennt die funktionale Programmierung noch eine Reihe von Konzepten, die in
der Modellierung bislang nicht verwendet werden. Dazu gehören höherere, also funktionswertige
Funktionen, die auch Funktionen als Parameter erhalten können, sowie das Prinzip der referentiellen Transparenz, das man sich als Grundlage für ein unbegrenztes Undo vorstellen kann.
Zwei Beispiele mögen die Nützlichkeit des funktionalen Paradigmas für die Modellierung
demonstrieren.
2.5. GENERATIVES MODELLIEREN
25
Deformationen parametergesteuert angewandt
Gegeben die Aufgabe, einen einfachen Zaun entlang einer Freiform-Kurve in der xy-Grundfläche zu modellieren. Die senkrechten Sprossen des Zaunes seien Zylinder mit elliptischem
Querschnitt und die kürzere Hauptachse einer Sprosse soll in Tangentenrichtung der Kurve orientiert sein. - Diese mit einem konventionellen Modeller mühsame Aufgabe ist offensichtlich empfindlich gegen Änderung der zugrundeliegenden Freiformkurve und gegen
Änderungen der Zaunhöhe. Funktional gesehen sind dies aber nur Eingabeparameter des
Konstruktionsprozesses.
Austauschen von Stilelementen
Neben Zaunverlauf und Höhe kann aber auch die Form der Sprossen als Eingabeparameter der Konstruktion gesehen werden. Angenommen, die Form der Sprossen soll geändert
werden, die Zylinderform wird durch einen Kegelabschnitt ersetzt. Es ist nicht einzusehen,
warum die Konstruktion nun komplett wiederholt werden sollte. – Würde man diese Änderbarkeit bei einem Modeller mit parametrisierbaren Modifier-Bäumen noch mit Hilfe der
Referenztechnik realisieren, so stöße man aber spätestens an Grenzen, wo verlangt wird, an
den Einzelelementen noch Modifikationen vorzunehmen, die von der Stelle abhängig ist, wo
die Elemente eingesetzt werden, wie etwa das Blending benachbarter Sprossen, oder wenn
die Sprossen parameterabhängig verformt werden sollen.
Die Integration funktionaler Konzepte ist der Kern der Generativen Modellierung. Dabei
betrachtet man die Funktionen als Erzeugende der Geometrie, die geometrischen Objekte und ihre Transformationen werden als Abbildungen betrachtet. Dies ist die konsequente Weiterführung
von Ideen wie einem Modifier-Baum, und die funktionale Programmierung ist das dazu passende Programmierparadigma wegen der Entsprechung von Funktionen eines Programms und den
Werkzeugen eines Modellers, und weil in beiden Fällen die wichtigste Verknüpfung die Komposition ist. Somit findet man hier eine theoretisch fundierte Grundlage, um den Konstruktionsprozeß
eines Modells zu beschreiben. Andererseits ist nicht klar, ob eine allgemeine funktionale Programmiersprache das geeignete Werkzeug ist, um wirkliche Modeller zu implementieren, oder ob
man Entsprechendes innerhalb des objektorientierten Paradigmas verwirklichen kann, wenn man
Abbildungen über Metaobjekte implementiert. Beide Ansätze werden in Kapitel 4 untersucht.
2.5.2
Paradigmenwechsel
Führt man die Idee des Modellierens mit Abbildungen konsequent weiter, bietet es sich an, auch
die Basiselemente der Geometrie, Kurven und Flächen, nicht einfach als Objekte im Sinne der
OOP zu begreifen, sondern ebenfalls funktional, als Abbildungen. Kurven werden in der generativen Modellierung daher in der parametrischen Form als Abbildungen eines Intervalls R ! R3
aufgefaßt, Flächen als Abbildungen von einem Intervall R2 ! R3. Diese Sichtweise gestattet eine
elegante Definition von Werkzeugen, die Flächen oder Kurven bearbeiten, einfach als Transformationen R3 ! R3 , sodaß eine transformierte Fläche als Komposition von Funktionen R2 ! R3
! R3 und eine transformierte Kurve als Funktion R ! R3 ! R3 begriffen werden kann, wobei
die gleiche Transformation benutzt werden kann.
So wird auch die natürliche Bedeutung höherer Funktionen deutlich: Eine Operation, die aus
einer Fläche eine neue, zum Beispiel deformierte, Fläche erzeugt, ist als Funktion mit folgender
Signatur anzusehen:
surface_deformation: ( R2 ! R3 ) ! ( R2 ! R3 )
Will man eine Konstruktion definieren, wo an einer bestimmten Stelle ein solches Werkzeug
benutzt wird, dieses Werkzeug aber austauschbar sein soll, so realisiert man dies, indem die Kon-
26
KAPITEL 2. EVOLUTION DER GEOMETRISCHEN MODELLIERUNG
struktion parametrisiert wird und der entsprechende Parameter surface_deformation eben
gerade diese Signatur erhält. Auf diese Weise realisiert man die Typsicherheit, und zwar vorzugsweise in einer für den Benutzer transparenten Weise.
Ein modelliertes Objekt wird so nicht mehr nur als eine hochstrukturierte Verkettung von Daten angesehen, sondern kann selber Rückgabewert einer Abbildung sein. Damit realisiert man die
Unabhängigkeit von zum Teil interaktiv spezifizierten Eingabedaten und der abstrakten Verknüpfung dieser Parameter, und in offensichtlicher Weise sind so die angestrebten Ziele Änderbarkeit
und Wiederverwendbarkeit erreicht.
Damit hat einen Paradigmenwechsel vollzogen, denn Objekt und Werkzeug sind nunmehr
synonym zu verwenden.
2.6
Zusammenfassung
Ordnet man die Evolution von Modelliertechniken in einen Zusammenhang, um Entwicklungstendenzen abzulesen, so ist zu beobachten, daß Änderbarkeit und Wiederverwendbarkeit von Modellen zunehmend an Bedeutung gewinnt. Dies wird dabei mit den Mitteln des objektorientierten
Paradigmas in der Weise realisiert, daß Slots, Eigenschaftsfelder von Objekten, nicht nur Werte
enthalten können, sondern auch Verweise auf andere Objekte. Damit sieht man eine Szene zusammen mit dem Produktionsprozeß als azyklischen Graphen von Objekten, wobei die Kanten
Modifikationen darstellen, Übergänge von einem Ausgangsbjekt zu einem modifizierten Objekt.
Diese Denkweise ermöglicht auch Konzepte wie einen Modifier-Stack, stellt also einen Beweis
für die Tragfähigkeit des objektorientierten Paradigmas dar.
Andererseits fehlt es in konventionellen Modellern an Möglichkeiten, um funktionale Abhängigkeiten zwischen Objekten auszudrücken. Automatische Berechnung von Objektparametern
modellieren zu können ist aber wichtig, wenn man Gegenstände oder ganze Teilszenen auf hohem
Niveau manipulieren möchte, und wenn man viele Gegenstände mit gleichartigen Konstruktionsschritten hat, die man gruppieren möchte, um eine Bibliothek von benutzerdefinierten parametrisierten Werkzeugen anzulegen.
Die Generative Modellierung basiert auf dem Konzept der Abbildung. Zum einen kann man
in diesem Zusammenhang sehr allgemein jede Veränderung bei der Modellierung einer Szene als
Ergebnis einer Abbildung bezeichnen, andererseits kann man mit geeigneten Abbildungen auch
statische Abhängigkeiten zwischen Objekten ausdrücken.
Man kann daher davon sprechen, daß Modellierung funktional objektorientiert ist. Was dies im
einzelnen bedeutet und wie die Idee des Modellierens mit Abbildungen präzisiert werden kann, ist
Gegenstand dieser Arbeit und einige Ansätze dazu werden in den folgenden Kapiteln vorgestellt
und untersucht.
Kapitel 3
Grundlagen
Es gibt eine ganze Reihe verschiedener Ansätze zur Definition von virtuellen Welten. In diesem
Kapitel soll die Bandbreite der Möglichkeiten aufgezeigt werden, wie in der Praxis modelliert
wird. Zum einen bewegt sich dies auf der Ebene von Dateiformaten, zum anderen gibt es Programmierschnittstellen, APIs (application programmer interfaces), die den Zugriff auf 3D-Objekte auf
einer hohen Abstraktionsstufe gestatten. Daneben sind an Universitäten Programmpakete zur wissenschaftlichen Visualisierung entstanden, und es existieren Arbeiten zum Softwaredesign von
Klassenbibliotheken, bei denen der Schwerpunkt auf das Finden einer wohldefinierten Klassenhierarchie als Grundlage für spätere Erweiterungen gelegt wurde.
Von zunehmender Wichtigkeit im Zuge der Ausweitung des World Wide Web sind Beschreibungssprachen, um dreidimensionale Geometrien über Netzwerke zu schicken. Dies geschieht
zum Teil mit der Intention, Anwendern an verschiedenen Orten die Möglichkeit zu bieten, sich
in einem gemeinsamen virtuellen Raum zu treffen. Die Standardsprache für diesen Zweck ist
die ’virtual reality modeling language’ VRML, ein ASCII-Dateiformat für Geometrien. Nachdem
sich die erste Version dieser Beschreibungssprache als unzulänglich für einige der angestrebten
Anwendungen erwiesen hat, wurde kontrovers diskutiert, wie ein zukünftiges Dateiformat aussehen könnte, das die Möglichkeiten von VRML erweitert. Im Zusammenhang dieser Arbeit ist
dabei vor allem die Frage von Interesse, auf welcher Ebene Modellierung stattfinden soll, und
welche Ausdruckskraft ein Dateiformat hat. – Ein Vergleich von Modellieransätzen auf Basis von
Dateiformaten ist dabei zweifellos fundierter als der direkte Vergleich von interaktiven Modellern,
die sich nur schwer formalisieren lassen, denn es läßt sich damit feststellen, welche Lösungen für
bestimmte Modellierprobleme formuliert werden können.
In einer Seminararbeit von Oliver Jucknath [Juc95] wird der Versuch gemacht, Kriterien für
eine Programmiersprache festzulegen, die zur Beschreibung von dreidimensionalen Objekten und
ganzen Szenen geeignet ist, vor allem im Hinblick auf ihre Verwendung als Dateiformat. Jucknath
weist dabei auf eine ganze Reihe von Problemen im Zusammenhang mit der Ausdrucksfähigkeit
und der Modelliereffizienz konventioneller Modeller hin, die zum Teil auch in dieser Arbeit schon
angesprochen worden sind. Er präsentiert die Wendeltreppe als Paradebeispiel eines hochstrukturierten Modells, das für konventionelle Modeller problematisch ist und empfindlich gegenüber
Änderungen der Konstruktionsvorgaben. Funktional gesehen sind Stufenhöhe, Rotationswinkel
pro Stufe und Form einer Stufe aber nichts als Eingabeparameter eines Modells. Ein Dateiformat
besitzt aber nicht nur eine Bedeutung für die Ein- und Ausgabe definierter Szenen, sondern auch
für die interne Arbeitsweise eines Modellers und die Möglichkeiten, die er einem Benutzer bietet.
Denn wenn in einer Ausgabedatei ein strukturiertes Objekt beschrieben werden soll, so muß der
Benutzer auch eine Möglichkeit besitzen, dem Modeller diese Struktur mitzuteilen. Das Problem
besteht also darin, im interaktiven Prozeß eine Art Programmierung zuzulassen. Jucknath schlägt
27
28
KAPITEL 3. GRUNDLAGEN
vor, dem Benutzer eine hybride Ansicht des Modells zu präsentieren, einerseits die dreidimesionale Darstellung zusammen mit einer Werkzeugleiste wie in einem konventionellen Modeller,
parallel dazu aber ein aus den Manipulationen in der Szene generiertes Skript, das bei der Benutzung eines Werkzeugs um die entsprechenden Funktionsaufrufe ergänzt wird. Das bedeutet,
daß der Programmtext zum größten Teil automatisch generiert wird, der Benutzer hat aber auch
die Möglichkeit, zwischen dreidimensionaler Ansicht und textueller Repräsentation zu wechseln.
Einerseits gibt es nämlich Aufgaben, die einfacher direkt in 3D zu lösen sind, anderes kann aber
symbolisch leichter ausgedrückt werden. Das bedeutet, man erspart dem Benutzer nicht gänzlich
die Programmierarbeit, fordert aber auch nicht, daß der gesamte Produktionsprozeß abstrakt und
ohne direkten Bezug zur dreidimensionalen Ansicht erfolgen muß.
Neben dieser hybriden Eingabemethode schlägt Jucknath in seinem Papier auch einen graphischen Programmieransatz vor. In Programmen wie dem Bildverarbeitungssystem Khoros
können sogenannte Dataflow-Maschinen zusammengesetzt werden. Dabei werden Werkzeuge
durch grafische Icons repräsentiert, die über Verbindungsmöglichkeiten zu anderen Icons verfügen. Durch Ziehen von Verbindungslinien kann man den Datenfluß lenken und Ausgabeparameter
mit Eingabeparametern verbinden. Ein solcher Datenflußgraph entspricht zunächst einem straightline-Programm, das weder Verzweigungen noch Schleifen noch Rekursion kennt. Schleifen und
Verzweigungen erhält man dann durch spezielle Werkzeuge, die Abbruchbedingung bei zyklischen Verbindungen spezifizieren. Rekursion erhält man erst, wenn man das Konzept der Gruppierung von Werkzeugen zu neuen Werkzeugen einsetzt, was einer Funktionsdefinition bzw. der
Idee der benutzerdefinierten Werkzeugbibliothek entspricht. Rekursion ist dann die Möglichkeit,
ein neues Icon bereits in dem Graphen einzusetzen, das dieses Werkzeug definiert.
Besitzt dieser Ansatz auch die gleiche Ausdruckskraft wie herkömmliches Programmieren,
so ist das Zusammensetzen doch sehr mühsam, wenn die Werkzeuge auf einem sehr niedrigen
Level arbeiten, etwa auf der Ebene von Ausdrücken einer Programmiersprache. Der Ansatz der
Dataflow-Maschine ist daher im Zusammenhang mit komponentenbasierter Programmierung zu
sehen, wo die Icons sehr mächtige Operationen repräsentieren, die man flexibel zusammensetzen
kann. – Das Zusammensetzen von Abbildungen (u; v) 7! (x; y; z) bewegt sich jedoch zunächst
auf einem vergleichsweise niedrigen Abstraktionsgrad, komponentenbasierte Programmierung ist
mehr beim Programmieren im Großen anzuwenden.
Ein weiteres Problem, auf das Jucknath in seiner Arbeit hinweist, ist die Parameterflut, die man
nicht vermeiden kann, wenn man parametrisierte Objekte zu einem neuen Objekt zusammensetzt.
Das neue Werkzeug erhält ja zunächst die Summe der Parameter aller Unterobjekte als eigene Eingabeparameter. Besonders bei komplizierten Objekten wie etwa einem Einfamilienhaus kann man
sich viele mögliche Parameter vorstellen, die ein Anwender nachträglich noch verändern können
möchte. Dazu mag die Form von Fenstern gehören, die Anzahl der Stockwerke, vielleicht sogar
der gesamte Grundriß als ein komplexer Parameter, ganz abgesehen von Zierobjekten und Ornamenten, die in bestimmter regelmäßiger Weise an einer Fassade angebracht werden sollen. Stellt
man sich ein Dateiformat vor, das all diese Änderungsmöglichkeiten in einer strukturierten Weise
ermöglicht, so muß eine Parametergruppierung sattfinden, man sollte also in der Lage sein, eine
Liste von Parametern komplett auszutauschen. Komplexe Parameter sind dabei als active styles
vorzustellen, die selbständig Veränderungen an einem Modell durchführen können. Dies ist aber
nur eine andere Sprechweise für Abbildungen als Parameter, auch hier wird also die Notwendigkeit von höheren Funktionen deutlich. Daher kann man viele Kriterien, die Jucknath in seinem
Papier nennt, als Beleg für die Notwendigkeit eines Dateiformates anführen, das funktionale Konzepte integriert. Im Rahmen dieser Arbeit ist dabei die Frage nach dem richtigen Dateikonzept für
die Generative Modellierung von Interesse, vor allem in Hinblick auf einige Unzulänglichkeiten
der im folgenden vorgestellten GENMOD-Programmiersprache.
3.1. GENMOD-PROGRAMMIERSPRACHE NACH SNYDER
3.1
29
GENMOD-Programmiersprache nach Snyder
Die Idee der Generativen Modellierung wurde ursprünglich in einem Buch von John M. Snyder
[Sny92] am California Institure of Technology (CalTech) vorgestellt. Darin beschreibt er einen
alternativen Weg der Modellierung, und zwar über eine interpretierte Programmiersprache. Das
bedeutet, Snyder geht vollkommen ab von der herkömmlichen Idee eines interaktiven Modellers
und zwingt den Benutzer, die Struktur seiner Gegenstände zu verstehen, um diese auf einer hohen
Abstraktionsstufe über Abbildungen zu beschreiben. James Kajiya schreibt im Vorwort zu diesem
Buch euphorisch:
Before this work, I thought about a shape as a collection of polygons, or a sculpted surface.
But that view is very limited. With a sculpted surface there’s really no difference between
a spoon shape and a chair shape; it’s all a matter of positioning the control points in the
right places. But a spoon shape has an inner logic, shared by all spoons – and that logic is
completely different from that of a chair.
John and I have spent many hours tying to discover the logic of different everyday shapes.
It’s an intellectually challenging and exciting endeavour, one that is quite pleasurable when
one hits on the right logic of a shape. Because of this, shapes are no longer just inscrutable
lumps, but a series of puzzles – sometimes easy and sometimes difficult.
Es geht bei der Generativen Modellierung also darum, eine Möglichkeit zu finden, die innere
Logik von Gegenständen zu explizieren. Snyder definiert ein generatives Modell als eine Form,
die durch die kontinuierliche Transformation einer anderen Form, des Generators, entsteht. Das
Paradebeispiel für eine solche Form ist die Sweep-Fläche, der verallgemeinerte Zylinder, der dadurch entsteht, daß eine geschlossene Kurve eine Raumkurve entlanggezogen wird. Die Transformation ist in diesem Fall einfach eine Bewegung durch den Raum, doch im allgemeinen können
generative Modelle aus einer großen Anzahl von Elementaroperationen zusammengesetzt sein.
Bei solchen komplexen Werkzeugen wie dem Sweep spricht er von Metashapes, damit wird die
Äquivalenz von Gegenstand und Werkzeug in generativen Modellen ausgedrückt.
Snyder stellt dabei zunächst ganz abstrakt Kriterien für die gesuchte Beschreibung von dreidimensionalen Modellen auf. Unter anderem führt er an:
Abgeschlossenheit unter Komposition
Größe der Klasse darstellbarer Modelle
Kompaktheit der Darstellung
Kontrollierbarkeit des Modells
Änderbarkeit
Neben dem Problem, eine zutreffende Sprache für die Modellierung zu finden, behandelt Snyder in seinem Buch vor allem Ideen zur Intervallrechnung, mit denen sich unter anderem das
Lösen von impliziten Gleichungen effizient und mit kontrollierter Genauigkeit durchführen läßt.
Dies ist insbesondere wichtig, um constraint-Methoden verwenden zu können, und um implizite
Funktionen zu explizitisieren.
Der für die Programmiersprache gewählte Ansatz ist ein prozeduraler. Die Snyder-Gruppe
in Pasadena hat dazu einen Interpreter für eine C-ähnliche Sprache konstruiert, die GENMODProgrammiersprache. Die Interpreter-Fähigkeit ist deshalb wichtig, weil der Modellierprozeß als
interaktive Programmierung gedacht ist und die Turnaround-Zeiten bei Kompilation als zu lang
angesehen wurden. Snyder sieht durchaus die Problematik, die in der Wahl einer solchen Benutzerschnittstelle liegt, und schlägt die Benutzung von GENMOD im wesentlichen zwei Gruppen vor,
und zwar Programmierern und Forschern. Programmierer haben dabei die Aufgabe, das System
KAPITEL 3. GRUNDLAGEN
30
an bestimmte Aufgabengebiete anzupassen, sodaß Anwender nicht mit der Sprache als solcher in
Berührung kommen, für Forscher andererseits ist nach Snyder die Flexibilität in der Modellierung
wichtiger ist als ein graphisches Benutzerinterface.
In GENMOD lassen sich C-Funktionen schreiben, die als Input-Parameter Objekte vom Typ
MAN erhalten und daraus ein neues MAN-Objekt zusammenbauen. Solche Funktionen sind daher
gleichbedeutend mit Metashapes und werden in der Terminologie der Generativen Modellierung
Operatoren genannt. Der Datentyp MAN – für ’manifold’, Mannigfaltigkeit – enthält allgemein
eine Abbildung Rm ! Rn. Der Name impliziert, daß MAN-Objekte ganze Mannigfaltigkeiten enthalten, dem ist jedoch nicht so. Zwar können mehrere parametrische Patches zu einem gemeinsamen Parameterbereich verbunden werden, doch ein MAN kann nicht mehrere Parameterbereiche
enthalten, wie es bei Mannigfaltigkeiten mit mehreren Karten der Fall ist.
Die GENMOD-Programmiersprache stellt eine begrenzte Menge grundlegender Operatoren
zur Verfügung, diese Grundoperationen sind dabei abgeschlossen in dem Sinne, daß von einem
Operator als Rückgabewert stets eine Abbildung MAN, ein Vektor MAN_ARRAY oder eine Matrix
MAN_MATRIX von Abbildungen erzeugt wird. Diese Grundoperationen sind dabei vollständig für
eine sehr große Klasse von Formen, sowohl für parametrische Funktionen als auch für implizite
Kurven und Flächen. Im einzelnen gibt es folgende Arten von Grundoperatoren:
Elementare Operationen
Das sind Konstanten und Koordinatenfunktionen, sowie arithmetische, logische, und mathematische Funktionen, dazu Vektor- und Matrizenfunktionen.
Differentiation, Integration
Elementaroperationen gestatten symbolische Differentiation, zusammengesetzte Operationen werden rekursiv mit Hilfe der Ableitungen der Einzelfunktionen differenziert. Kann
nicht symbolisch abgeleitet werden, so geschieht dies in numerischer Näherung. Das Integral einer Funktion bezüglich einer Variablen ist ein Skalar, hängt diese Funktion noch von
anderen Parametern ab, so ist das Integral eine Funktion dieser anderen Parameter.
Indizieren, Branching
Es können Funktionen bezüglich einer Variablen “aneinandergehängt” werden, die Parameterintervalle der einzelnen Funktionen werden zu einem kontinuierlichen Parameterbereich
zusammengesetzt. Auf diese Weise lassen sich Funktionen tabellieren, insbesondere erhält
man so Polygonzüge. Kurven und Flächen können in tabellierter Form aus Dateien eingelesen werden.
Umkehrung einer monotonen Funktion
Es kann die Inverse einer Funktion R ! R bestimmt werden, zu x aus dem Bildbereich
erhält man die Stelle t, wo f (t ) = x gilt.
Lösung eines n-dimensionalen constraints
Dies ist im wesentlichen die Lösung eine Systems impliziter Gleichungen. Der Rückgabewert ist ein einzelner Punkt, der den constraint erfüllt, aber von den weiteren Parametern der
Zielfunktionen abhängen kann. Um den Schnitt zweier Flächen p(u; v) und q(s; t ) bezüglich
gegebenem t zu finden, kann man zum Beispiel den Ansatz MAN c=solve(p(u; v) = q(s; t))
wählen, t parametrisiert dann die zurückgegebene Kurve c.
Minimieren unter Nebenbedingungen
Dies erfolgt ähnglich wie die constraint-solution, die Nebenbedingungen spezifizieren hier
jedoch zunächst zulässige Gebiete im Parameterraum der zu minimierenden Funktion. Der
3.1. GENMOD-PROGRAMMIERSPRACHE NACH SNYDER
31
Rückgabewert ist ein Punkt, wo die zu minimierende Funktion ihr Minimum über den
zulässigen Punkten annimmt.
Lösen von Anfangswertproblemen von Differentialgleichungen erster Ordnung
Mit diesem Operator kann man die Trajektorie in einem Vektorfeld f bestimmen, indem
für einen Anfangspunkt y0 die Funktion y berechnet wird, für die gilt, daß y(t0) = y0 und
y0 (t ) = f (y; t ) ist.
Komplizierte zusammengesetzte Operatoren oder Metashapes sollen es ermöglichen, Formen
auf einem hohen Abstraktionsniveau zu beschreiben und die funktionale Gliederung eines Gegenstandes in den Vordergrund zu stellen, statt wie bei herkömmlichen Modellern die sehr anschauliche, aber letztendlich strukturverschleiernde 3D-Darstellung zur Grundlage der Arbeit zu machen,
wo einfach solange an den verschiedenen Kontrollpunkten von Freiformflächen manipuliert wird,
bis das Ergebnis einigermaßen stimmt.
Andererseits verlangen viele Codefragmente, die solche Metashapes beschreiben, vom Modellierer, der in diesem Fall zu einem Programmierer wird, eine nicht unerhebliche räumliche
Vorstellungskraft, wenn komplizierte Verknüpfungen zwischen den verschiedenen Koordinaten
der jeweiligen Input-Kurven ausgeführt werden.
Als Beispiel für die Definition eines höheren Operators soll die Definition eines speziellen Sweep dienen, des ’wire product’. Dabei geht es darum, ein zweidimensionales Profil, die
’cross-curve’ γ(u), eine ebenfalls zweidimensionale ’wire curve’ δ(v) entlangzuziehen. Der Tangentialvektor tδ (v) = (t1(v); t2(v)) der wire-curve wird benutzt, um durch die Transformation
(x; y) 7! (,y; x) einen Vektor zu erzeugen, der senkrecht auf der wire-curve steht:
tδ (v) =
dδ1
dv (v)
dδ2
dv (v)
!
=
t1(v)
t2(v)
!
;
!
0
1
,
t2(v)
B γ (v) t1(v) + δ(u) C
S(u v) = @ 1
A
;
γ2 (v)
In GENMOD sind die arithmetischen Operatoren von C überlagert, sodaß sie nicht nur auf
Zahlen, sondern auch auf Vektoren und Matrizen anwendbar sind, @ ist das kartesische Produkt,
mit dem Zahlen und Vektoren zu neuen Vektoren konkateniert werden. Zudem ist es möglich,
extern mit einem 2D-Editor gezeichnete Kurven einzulesen. In GENMOD realisiert das man wireproduct S(u; v) dann mit folgendem Codefragment:
MAN m_wire(MAN cross, MAN wire)
{
MAN that = m_derivative(wire,wire->input_list[0]);
MAN t = m_normalize(that);
MAN n = @(-t[1], t[0]);
return @(n*cross[0]+wire, cross[1]);
}
MAN cross = m_crv("cross.crv",m_x(0));
MAN wire = m_crv("wire.crv", m_x(1));
MAN racket_frame = m_wire(cross,wire);
Mit diesem Beispiel wird demonstriert, wie eine Abbildung aufgebaut wird. Was geschieht
nun mit dem Resultat, dem zusammengesetzten Operator racket_frame? Ganz entscheidend für
den Abbildungsbegriff, den Snyder verwendet, ist das Konzept der Operatormethoden, die auf
Operatoren angewandt werden können.
KAPITEL 3. GRUNDLAGEN
32
GENMOD unterstützt neben der Synthese von Operatoren nämlich auch die Analyse zusammengesetzter Abbildungen vom Typ MAN. Eine lokal rekursiven Operatormethode ist eine
Methode, deren Ergebnis für einen Operator P( f1; :::; fn) in einem Punkt p ausgerechnet werden
kann, wenn der Wert dieser Methode auf den Eingabefunktionen f1; :::; fn (vom Typ MAN) bekannt
ist. So ist beispielsweise die Auswertung eine lokal rekursive Methode für den Additionsoperator,
denn f + g kann berechnet werden, wenn die Werte von f und g bekannt sind. Berechnung des beR
stimmten
Integrals
dagegen
ist
nicht
lokal
rekursiv
für
die
Division,
denn
f =g kann nicht durch
R
R
f und g berechnet werden.
In GENMOD unterstützen sämtliche elementaren Operatoren die folgenden Operatormethoden:
Auswertung
Symbolisches Bilden der partiellen Ableitungen
Berechnung der Inklusionsfunktion
Auswertung ist für die Grundoperationen Integration, Umkehrung und Lösen des Anfangswertproblems zwar streng betrachtet nicht lokal rekursiv, kann aber durch mehrfaches Auswerten
der Eingabefunktionen berechnet werden. Differentiation ist für die elementaren arithmetischen
Funktionen durchaus lokal rekursiv, für Operatoren, die durch numerische Verfahren ausgerechnet werden wie das bestimmte Integral wird die Ableitung in numerischer Näherung ausgerechnet.
Der Differentiations-Operator gibt angewendet auf ein MAN-Objekt ebenfalls ein MAN-Objekt
zurück.
Die intervallwertige Inklusionsfunktion einer Funktion gibt an, wie stark ihre Werte über einem bestimmten Parameterbereich variieren. Genauer heißt das, 2 f ist eine Inklusionsfunktion
von f , wenn für alle x aus dem Parameterbereich von f gilt: f (x) 2 2 f (I ) für alle Intervalle I
mit x 2 I. Für die elementaren arithmetischen Operationen lassen sich die Inklusionsfunktionen
durch Intervallarithmetik bestimmen, wo sorgfältig darauf geachtet wird, Intervallgrenzen in die
richtige Richtung zu runden, wenn ein Intervall durch einen Operator auf ein anderes Intervall
abgebildet wird. Bei Funktionen wie Sinus, Wurzel etc. lassen sich die Bildintervalle durch Fallunterscheidungen bestimmen, im ungünstigsten Fall muß eine Maximum- bzw. Minimumsuche
erfolgen.
Es wird also ein nicht unerheblicher Aufwand getrieben, um nachzuverfolgen, welchen Wertebereich eine zusammengesetzte Funktion bezüglich eines Parameterintervalls hat, doch dies ist
Grundvoraussetzung für eine effiziente Implementierung der Operatoren zum Lösen einer Menge
logisch verknüpfter constraints und zum Minimieren unter Nebenbedingungen.
Beide Operatoren gehen dabei in ähnlicher Weise vor, indem ein Grundintervall immer weiter
aufgeteilt wird, die Inklusionsfunktionen der einzelnen constraints geben dann Auskunft darüber,
ob es sich lohnt, ein bestimmtes Teilintervall weiter zu betrachten. Wenn ein constraint in einem bestimmten Gebiet nicht erfüllt werden kann, so enthält seine Inklusionsfunktion den Wert
1 =Wahr nicht mehr, man hat somit ein eindeutiges Kriterium zur Beurteilung von Gebieten, und
beide Algorithmen können relativ einfach implementiert werden, weil die eigentliche Arbeit von
den Inklusionsmethoden der einzelnen Operatoren erledigt wird.
Diese beiden Operatoren haben außer bei Schnittbestimmungen noch viele weitere Einsatzmöglichkeiten in der Modellierung. Mit Hilfe der Minimierung unter Nebenbedingungen kann
man Raytracing implementieren, wenn man den Punkt auf einem Strahl sucht, der einer parametrischen Fläche am nächsten liegt, und man kann eine Kollisionsdetektion durchführen, wenn man
nach dem minimalen Abstand zwischen zwei Freiformflächen sucht. In einfacher Weise kann man
auch Offsetkurven und -flächen zu einer gegebenen Menge S berechnen indem man Punkte der
folgenden Menge bestimmen läßt: fxj miny2S dist(x; y) = rg.
3.2. MRT
33
Mit GENMOD ist es sogar gelungen, CSG auf Freiform-Gegenständen durchzuführen. Dazu
wird mit Minimierung unter Nebenbedingungen der Schnitt zweier Oberflächen durchgeführt, indem die Projektion der Schnittkurve in die beiden Parameterbereiche der Flächen berechnet wird.
Dies sind möglicherweise mehrere geschlossene Kurven, die einen Parameterbereich in zwei Teile aufteilen, die die Teile der Fläche repräsentieren, die im Inneren oder auf der Oberfläche des
zusammengesetzten Gegenstands liegen, und entsprechend angezeigt oder weggelassen werden.
Damit werden beide Oberflächen als getrimmte Patches dargestellt. Dieses Verfahren kann man
nun aber fortsetzen, bei Schnitt mit einem dritten Freiform-Körper entstehen wiederum Schnittkurven mit den beiden ersten Körpern, und man kann schließlich die noch anzuzeigenden Teile
der Oberflächen aller Körper bestimmen, wenn man die Punkte kennt, wo sich die projizierten
Schnittkurven in allen Parameterbereichen schneiden. Die Ränder der weggetrimmten Bereiche
erhält man schließlich durch stückweises Aneinandersetzen von Teilen dieser projizierten Schnittkurven.
Im weiteren Verlauf dieser Arbeit wird die von Snyder eingeführte Terminologie der Generativen Modellierung beibehalten. Das bedeutet, es wird auch bei den Genmod-Implementationen
in C++ und Haskell von elementaren und höheren Operatoren gesprochen, die ihrerseits synonym
zu Werkzeugen und Metashapes sind. Syntaktisch gesehen handelt es sich dabei jedoch stets um
die gleichen Objekte, in der Rückführung all dieser Begriffe auf ein gemeinsames Konzept liegt
gerade die Bedeutung des in 2.6 angesprochenen Paradigmenwechsels.
3.2
MRT
Das ’minimal rendering toolkit’ (MRT) ist eine objektorientierte Programmierplattform, die in der
Abteilung für Computergrafik der Universität Bonn entstanden ist. Es wird sowohl zur Forschung
als auch zur Lehre eingesetzt und besteht aus einer Menge von über Jahre gereiften, hochgradig portablen C++-Bibliotheken, in denen viele Konzepte und Ideen der Computergrafik realisiert
sind. Der Schwerpunkt und die ursprüngliche Motivation liegt dabei im Rendering komplexer Szenen begründet. So bietet das Paket die Möglichkeit, Szenen zu raytracen, oder eine approximative
Darstellung einer Szene unter Ausnutzung eventuell vorhandener Grafikhardware eines Rechners
darzustellen. Weiterhin gibt es die Möglichkeit, auf eine Szene das Radiosity-Verfahren anzuwenden, um den diffusen Lichtaustausch zwischen einzelnen planaren Patches einer adaptiven
Approximation der Oberflächen in der Szene zu berechnen. Das Ergebnis einer solchen Rechnung
ist eine physikalisch korrekt eingefärbte Szene mit adaptiv verfeinerten BReps der Objekte.
Neben den Bibliotheken enthält das System auch einige ausführbare Anwendungen, zum einen
einen Raytracer, der auf Kommandozeilenebene arbeitet und eine Bilddatei berechnet, zum anderen einen interaktiven Viewer für statische Szenen.
Forschungsgegenstand ist beim MRT neben den eigentlichen computergrafischen Algorithmen
auch die Frage, welches Softwaredesign nötig ist, um Erweiterbarkeit und Wiederverwendbarkeit
der Algorithmen zu ermöglichen. Das Paket ist in C++ implementiert und besteht in Version 2.07
aus insgesamt über 550 Klassen, die nach Schwerpunktgebieten in die folgenden Bibliotheken
aufgegliedert sind.
gen: Generische Funktionen und Datenstrukturen
In dieser Bibliothek liegen über 100 Klassen von Basis-Datenstrukturen, Vektoren, Matrizen, Listen und Datenstrukturen für die Farbenbehandlung.
cgi: 2D-API
CGI ist ein ISO-Standard zum plattformunabhängigen Zeichnen im Zweidimensionalen, es
KAPITEL 3. GRUNDLAGEN
34
gibt hier eine Reihe von Befehlen zum Zeichnen von Linien, Kreisen, Ellipsen, Polygonen,
zum Setzen von Zeichenattributen, zum Farbmanagement, zur Ausgabe von Buchstaben und
Strings, und eine abstrakte Schnittstelle zu 2D-Eingabegeräten.
cgi3d: 3D-API
Die 3D-Erweiterung von cgi behandelt das approximative Rendering auf Systemen, die
über keine Hardwareunterstützung verfügen, und emuliert dies softwaremäßig durch einen
Scanline-Renderer.
pen: Graphical user interface (GUI)
Penguin ist ein objektorientiertes GUI, das es ermöglicht, Ereignisse, die von einem grafischen Fenstermanager bei der Benutzerinteraktion generiert werden, plattformunabhängig
auf Funktionen eines Programms abzubilden. Es stellt eine erweiterbare Menge von Dialogobjekten (Widgets) zur Interaktion mit dem Anwender zur Verfügung.
apps3d: Anwendungen
Dies sind vier Anwendungen, die die Bibliotheken integrieren, ein Kommandozeilen-Raytracer, und drei interaktive Viewer, darunter einer mit Penguin-Interface und ein Viewer
speziell für Radiosity-Anwendungen.
mrt: Kern des Rendering-Toolkit
Hier findet sich eine Hierarchie von über 280 Klassen, die Zugriff auf sämtliche Elemente
einer Szene bieten. Zum einen findet man hier die Definition von Szenenobjekten wie Lichtern, Oberflächeneigenschaften und geometrischen Grundobjekten, sowie eine Implementation von BReps, die CSG erlauben. Hier schließlich ist hier auch der eigentliche Raytracer
zu finden, in den ständig neue Verfahren wie volume rendering, photon- und path-tracing
integriert werden.
Das MRT-Paket enthält somit die Datenstrukturen, die für die Modellierung relevant sind. In
diesem Zusammenhang ist vor allem das Objekt-Konzept des MRT von Interesse, das durch die
Basisklasse vorgegeben ist, von der alle anderen Gegenstände abgeleitet sind. Die Schnittstelle für
Gegenstände, die von einer zweidimensionalen Oberfläche begrenzt werden, befindet sich in der
Klasse t_Object und ist ausschnittsweise folgendermaßen gegeben:
class t_Object
{
...
/** Returns bounding volume object. Currently this is an
axis-aligned bounding box.*/
virtual t_BVol
boundingVolume () const;
/** This abstract method tests for intersections between a given ray
and this object and calculates the closest intersection point.*/
virtual t_Bool
intersect
(t_Ray& ray) = 0;
/** This method generates an approximation stored in the object.
qualityControl(in) controls the quality of the approximation:
q = -n (n<10 integral) <=> reduce
by n steps
q = +n,(n<10 integral) <=> increase by n steps
q in [-0.5, 0.5]
<=> set ’absolute’ quality, default=0,
To force refinement of planar faces use offset 10 */
virtual t_Bool
approxShape
(t_Real qualityControl=0);
3.2. MRT
35
/** Inverse mapping function [x,y,z] --> [u,v] for texture
mapping, returns the 3D-point mapped onto [0,1]x[0,1]. */
virtual t_Bool
mapInvers
(const t_3DVector& xyzPoint,
t_2DVector& uvPoint) const;
/** This method moves an arbitrary vertex to the object boundary by
intersecting a ray emanating from the vertex with the object as
a default implementation. */
virtual void
adjustVertex
(t_Vertex* v);
...
};
Die Eingabe einer Szene in den MRT geschieht durch Parsen einer Szenenbeschreibungsdatei. Dieses System ist modular in dem Sinne, daß Parser austauschbar sind und nur verlangt wird,
daß ein Parser schließlich ein Szenenobjekt liefert, das im weiteren Verlauf Grundlage der Berechnungen ist. Eine Szene enthält dabei sämtliche geometrischen Objekte mit den notwendigen
Transformationen, kann anstelle eines Objektes aber auch rekursiv eine weitere Subszene enthalten. Man spricht deshalb auch vom Szenenbaum.
Der entscheidende Aspekt bei dieser Herangehensweise ist, daß Gegenstände im MRT in einer Weise definiert sind, daß sie beide wesentlichen Anforderungen für Modelle erfüllen, die Umwandlung in eine approximative Darstellung mit einstellbarerer Genauigkeit durch die Methode
approxShape() und den Schnitt der Oberfläche mit einem Strahl durch intersect().
Der Vorteil dabei ist, daß das abstrakte Objekt weiterhin die Schnittstelle für den Zugriff auf
die Gegenstände in der Szene bietet. Algorithmen können sich beider Zugänge zu einem Objekt
bedienen, wodurch sich manche Aufgaben viel effektiver lösen lassen, als wenn nur eine Repräsentation eines Gegenstands verfügbar wäre. Strahlschnitte lassen sich in vielen Bereichen verwenden, von der Kollisionsdetektion über Sichtbarkeitsbestimmungen bis zu Modellen für diffusen Lichtaustausch, und die Approximation profitiert ebenfalls von der Verbindung zur Objektbeschreibung, weil durch adjustVertex() lokale Verfeinerungen an einer Approximation vorgenommen werden können. Dies ist immens wichtig beispielsweise beim Radiosity-Verfahren, wo im
Laufe der Berechnung festgestellt wird, daß ein bestimmtes planares Patch einen relativ großen
Teil der Energie in der Szene erhält oder es dort eine große Änderung in der Lichtverteilung gibt,
weshalb dieses Patch feiner unterteilt werden muß.
MRT-Objekte haben dabei grundsätzlich eine geschlossene Oberfläche, entsprechen also der
Definition eines Gegenstandes wie sie in Abschnitt 2.1 gegeben wurde. Im MRT-Paket finden sich
demnach auch viele Standardprimitive, von Kugeln über Kegel und Quader bis zu Rotationsobjekten und Metaballs. Insbesondere sind Gegenstände konzeptionell als Patchkomplexe vorgesehen,
was sich auch bei der Approximation in sofern niederschlägt, als es mehrere Normalvektoren an
einer Ecke eines MRT-BReps geben kann. Auf diese Weise kann man Knicke in der Oberfläche eines Objektes modellieren, und dies wird bei der interaktiven Darstellung in sofern berücksichtigt,
als Farbinterpolation zwischen benachbarten planaren Patches, wie sie beim Gouraud-Shading
eingesetzt wird, nicht über Objektkanten hinweg durchgeführt wird, Kanten zeichnen sich also
durch Farbunterschiede deutliche ab.
Andererseits sind MRT-Objekte nicht gezwungen, intern eine Patchkomplex-Datenstruktur zu
benutzen, sondern es bleibt den einzelnen Objekten überlassen, Probleme zu lösen wie bei einem
adjustVertex() in der Nähe einer Kante herauszufinden, welchem der benachbarten Patches eine
gegebene BRep-Ecke zuzuordnen ist. Dies führt insbesondere an Knicken zu Problemen, etwa
wenn zwischen zwei BRep-Ecken auf dem Rand einer Kegelgrundfläche eine weitere Ecke eingefügt werden soll. Verfeinert man den BRep, indem der Mittelpunkt der Kante als neuer Eckpunkt
36
KAPITEL 3. GRUNDLAGEN
eingefügt wird, so hat adjustVertex() eigentlich nichts zu tun, denn die neue Ecke liegt als solche
ja korrekt auf dem Kegelboden, also auf der Oberfläche, wenn auch nicht auf dem Knick. Solche
Probleme werden dabei mit speziellen Heuristiken für jedes Objekt einzeln gelöst.
Neben geschlossenen Gegenständen sind im MRT aber auch verschiedene Klassen von Patches mit Rändern als Ableitungen von t_Object enthalten, wie Drei- und Vierecke, Kreisscheiben,
bikubische Bézierpatches und Höhenfelder. Diese werden als einzelne Objekte ohne Zusammenhangsinformationen behandelt.
Das ursprüngliche Eingabeformat des MRT sind msd-Dateien, (für mrt scene description) , die
im wesentlichen eine Beschreibung einer Szenenhierarchie enthalten, die genau dem schließlich
konstruierten Szenenbaum entspricht. Für sämtliche MRT-Objekte gibt es msd-Schlüsselworte,
die Objekte werden durch Angabe der Parameter beschriebenen, die vom Parser an den C++Konstruktor übergeben werden. Msd-Dateien können Zahlen- und Vektorvariablen enthalten, zusätzlich gibt es das Konzept der Referenzobjekte, sodaß zwei verschiedene Einträge im Szenengraphen auf das gleiche Objekt zeigen können, das nur einmal angelegt werden muß. Dies
ist insbesondere für datenintensive Objekte vorteilhaft, es gibt zum Beispiel Scanner-Objekte,
die Datensätze von 3D-Scans enthalten, die sehr groß sein können, oder BRep-Objekte, die im
indexed-face-set-Format eingelesen werden können. BRep-Objekte haben keine abstrakte Forminformation mehr, sind also nur durch eine planare Approximation gegeben, und führen auch damit den Strahlschnitt durch. Dabei kommen Datenstrukturen zur hierarchischen Raumunterteilung
zum Einsatz, die das Finden des getroffenen Polygons eines BReps dramatisch beschleunigen.
Msd-Dateien enthalten somit im wesentlichen eine statische Szene, die in ein einzelnes Bild
gerendert wird, es gibt im MRT augenblicklich noch keine Animationsobjekte, die es ermöglichen,
Szenengraphen von der Zeit abhängig zu machen, entsprechende Ansätze befinden sich jedoch in
der Entwicklung.
Der MRT bietet insgesamt also die Möglichkeit, fertige Szene mit verschiedenen Verfahren zu
rendern. Für die Modellierung von Szenen selbst liefert der MRT allerdings keine Unterstützung.
Diese Arbeit wird der MRT als Programmierplattform für die Implementation der Generativen Modellierung über eine C++-Klassenhierarchie eingesetzt. Damit wird erreicht, daß generative Modelle plattformübergreifend mit modernen Renderingmethoden dargestellt werden können.
Ohne die grundlegende Infrastruktur, die ein solch umfangreiches Paket von Klassenbibliotheken
bietet, wäre es nicht möglich gewesen, sich alleine auf die Modellieraspekte der Arbeit zu konzentrieren, dies ist ein Beleg für den Nutzen, den der MRT als wohlfundierte Arbeitsgrundlage auch
in Bereichen bringt, die sich nicht mit seinem Kerngebiet, dem Rendering, beschäftigen. Insbesondere das Objektkonzept des MRT bietet eine Möglichkeit, wie generative Modelle bis zum letzten
Schritt, dem Rendering, in der ihnen eigenen zusammengesetzten Form beibehalten werden können. Das bedeutet, daß die Voraussetzung dafür besteht, auch während des Renderingprozesses
selber die analytische Beschreibung generativ modellierter Kurven und Flächen zu benutzen. Aus
dieser Perspektive ist der MRT das ideale Werkzeug für die Darstellung generativer Modelle.
3.3. OPEN INVENTOR
37
3.3 Open Inventor
OpenInventor [WG94], [FG94] ist ein C++-API der Firma Silicon Graphics Inc. (SGI) zur Konstruktion von Szenengraphen. Es definiert darüberhinaus ein Dateiformat und ist vor allem wegen
seiner Verwendung als Quasi-Standard zur Beschreibung von 3D-Szenen von Bedeutung. Dieses
Format diente als Vorlage für die erste Version von VRML, dem plattformunabhängigen Standard für die Beschreibung von Geometrien und Szenen zur Übertragung über das World Wide
Web (WWW). Das C++-API setzt dabei auf der Grafikbibliothek OpenGL von SGI auf, einem
low-level-API zum Rendern approximativer Szenen, dessen umständliche, stackbasierte Programmierung anwenderfreundlich gekapselt wird. – Das Szenenkonzept von Inventor sieht dabei nicht
nur die Beschreibung von statischen Szenen vor, sondern es können auch ’events’ und ’engines’
definiert werden. Events werden von den Knoten im Szenengraphen verarbeitet, die wissen, wie
sie zu reagieren haben, engines dienen zur Animation von Teilen einer Szene oder zum Spezifizieren einzuhaltender Bedingungen.
Inventor-Knoten sind von der Basisklasse SoNode abgeleitet und lassen sich im wesentlichen
den folgenden Kategorien zuordnen:
Shape nodes enthalten die Geometrie eines Körpers
Property nodes verändern Eigenschaften von Körpern oder Szenen
Group nodes dienen zur Verwaltung von Subszenen
Daneben gibt es noch eine Reihe spezieller Knotentypen, zum Beispiel für Kameras und Lichter. Geometrien sind entweder Elementarobjekte wie Würfel, Kegel, Kugel, Zylinder und Text,
NURBS-Freiformkurven und -flächen, oder reguläre oder irreguläre Polygonnetze. Mit properties werden Materialien und Texturen vergeben, es können das für die Szene zu verwendende
Beleuchtungsmodell, die Verarbeitung von level-of-detail-Informationen, die Maßeinheiten und
atmosphärische Effekte definiert werden, ebenso wie Transformationen von Gegenständen oder
Subszenen.
Der Szenengraph ist geordnet, Graphen oder Subgraphen besitzen eine Liste, keine Menge,
von Söhnen. Das Setzen von Eigenschaften wird dadurch reihenfolgeabhängig, ändert man zwischen zwei Knoten die Materialfarbe, so wirkt sich das nur auf den zweiten Knoten aus. Transformationen werden akkumuliert, in sofern ist das Stack-Konzept von OpenGL in Inventor enthalten. Mit einem speziellen Gruppenknoten, einem ’separator’, lassen sich jedoch die momentanen
Eigenschaften sichern, nach Abarbeitung des darunterliegenden Subgraphen werden die alten Eigenschaften wiederhergestellt.
Inventor enthält neben einer Hierarchie von Knoten, nodes, auch eine Hierarchie von Aktionen, actions, zur Analyse eines Graphen. Dabei wird der Szenengraph traversiert und die einzelnen
Knoten ausgewertet. Aktionen sind einmal das Rendern einer Szene, dann die Berechnung einer bounding box, das Speichern in eine Datei, das Finden akkumulierter Transformationen, aber
auch der Strahlschnitt, das Suchen eines Pfades zu einem bestimmten Knoten und das Verteilen
von events. Knoten können auf Ereignisse mit dem Aufruf einer callback-Funktion reagieren, auf
diese Weise kann eine Anwendung mit einem Szenengraphen kommunizieren. Ein der Szene übergeordnetes Objekt, ein ’viewer’, kann zudem mit ’sensors’ ausgestattet werden, die in bestimmten
Situationen Ereignisse auslösen. – Inventor stellt dem Anwendungsprogrammierer durch die vielschichtige Ereignisverwaltung also viele Möglichkeiten zur Verfügung, eigene Funktionalität in
die vorgefertigte Struktur einzubringen.
Im Zusammenhang mit der Modellierung sind aber vor allem engines von Interesse. – Um
Abhängigkeiten zwischen Größen in einer Szene auszudrücken, ist es nicht zweckmäßig, Abbildungen durch callback-Funktionen einer Applikation zu realisieren. Vielmehr gehören diese
KAPITEL 3. GRUNDLAGEN
38
Abhängigkeiten zur Szene selber und müssen durch entsprechende Abbildungen beschrieben werden. Dazu gibt es eine Reihe spezieller engines, die in Snyders Terminologie in etwa Operatoren
entsprechen. So kann man Vektoren und Matrizen in einzelne Komponenten zerlegen und wieder zusammensetzen, es gibt Zähler, engines zur Interpolation zwischen Zahlen, Vektoren und
Rotationen, und es gibt ein SoCalculator-Objekt zur Berechnung arithmetischer Ausdrücke. Ein
calculator besitzt je acht feste Eingabevariablen vom Typ Float, a-h, und vom Typ Vec3f, A-H. Um
einen calculator zu spezifizieren, der zwei Zahlen a, f und zwei Vektoren A, B erhält, a von Grad
nach Radian konvertiert und , f (A B) berechnet, benutzt man folgenden Code:
SoCalculator* calc=new SoCalculator;
calc->expression.setValue(0,"oa = a * M_PI / 180");
calc->expression.setValue(1,"oA = -f * cross(A,B)");
Es gibt dabei feste Ausgabevariablen oa-od und oA-oD, das Ergebnis einer Rechnung kann
als Eingabe für das Datenfeld eines beliebigen Inventor-Knoten verwendet werden, wenn der Typ
übereinstimmt.
SoRotationXYZ* rot=new SoRotationXYZ;
rot->axis.connectFrom(&calc->oA);
rot->angle.connectFrom(&calc->oa);
Die Liste der Knoten, die innerhalb einer Gruppe auf rot folgen, werden also einer Rotation
unterzogen, die aus anderen Größen der Szene berechnet wird, zum Beispiel Richtungsvektoren
von Translationen, Höhe und Breite eines Würfels und so weiter. – Andere engines sind in der
Lage, einen relationalen Ausdruck auszuwerten und Wahr oder Falsch zurückzugeben, damit läßt
sich ein Ereignis triggern oder eine Bedingung überprüfen, beispielsweise eine Kollision von Gegenständen erkennen.
Modellierung ist auf diese Weise allerdings bloß sehr eingeschränkt möglich, da engines
nur Eigenschaftsfelder von Inventor-Knoten verknüpfen können. Eine Eigenschaft von NURBSKurven ist aber point, eine Funktion, die ein C-Array von 3D-Koordinaten übergeben bekommt.
Einzelne Kontrollpunkte lassen sich also nicht über engines steuern; im wesentlichen werden sie
daher bei Transformationen eingesetzt, etwa um Objekte aus Teilen zusammenszusetzen, deren
Bewegung wie bei Gelenken bestimmte Freiheitsgrade besitzt.
Engines sind daher zwar durchaus ein interessantes Konzept, ihre Verwendung innerhalb von
Inventor unterliegt aber Beschränkungen, die in ihrem Verwendungszweck als Steuerungsobjekte
für Animationen und zum Prüfen von Bedingungen begründet liegen.
3.4
GeomView
Geomview ist ein interaktives Programm zur Betrachtung und Manipulation von geometrischen
Objekten in Form von BReps, entstanden am Geometry Center der Universität von Minnesota in
Minneapolis. Es bietet insbesondere die Möglichkeit, mit passenden Projektionsverfahren nicht
nur Objekte im euklidischen, sondern auch im sphärischen und hyperbolischen Raum anzuzeigen,
sowie eine oder mehrere Projektionen mehrdimensionaler Objekte, zum Beispiel des vierdimensionalen Hyperkubus. Neben der Dimensionalität und dem Projektionsverfahren kann man auch
das Aussehen von Objekten definieren, einige Materialkonstanten setzen, die Objekte als wireframe, per hidden-line-removal oder Gouraud-schattiert anzeigen lassen, man kann Lichter setzen
und mehrere Ansichten (Kameras) mehrerer Szenen parallel in verschiedenen Fenstern interaktiv
manipulieren.
3.4. GEOMVIEW
39
Geomview arbeitet jedoch nicht allein als interaktives Betrachtungsprogramm, das Objektbeschreibungen aus Dateien einliest, sondern es gestattet anderen Programmen auch den Zugriff auf
Darstellung und Objekte. Dazu werden separate Anwendungsprogramme, Module, aus Geomview heraus gestartet und ihre Standardein- und ausgabe auf Geomview umgelenkt. Diese Programme können nun Kommandos der Geomview-Befehlssprache GCL (geomview command language) auf den Standardausgabekanal schreiben, das eigentliche Geomview-Programm verändert
die Szene entsprechend und kann Benutzerinteraktionen synchron oder asynchron an das Modul zurücksenden. Das Modul kann dazu Interesse an bestimmten Geomview-Ereignissen äußern,
etwa ’pick-events’, bei denen der Benutzer eine Seitenfläche eines BReps durch Mausklick anwählt. Geomview schickt diese Ereignisse in Form von GCL-Kommandos an die Standardeingabe
des Anwendungsprogramms, das nicht nur durch Zurückschicken veränderter Geometrien reagieren kann, sondern durch GCL-Kommandos Zugriff auf viele der inneren Einstellungen des
Host-Programmes besitzt. Die Kommandos fallen in die folgenden Kategorien:
Starten von Modulen, Kommunikation mit anderen Modulen, Lesen und Schreiben von
Geometrien, Kommunikation mit dem Betriebssystem und der Shell
Sämtliche Einstellungen der Darstellung, verwendete Räume und Projektionsverfahren, Materialien, Transformationen
Ereignisverwaltung
GCL selbst ist eine einfache, an LISP angelehnte Programmiersprache mit Funktionsaufrufen der Form (fct a b c), mit Verzweigungen, doch ohne eigene Variablen und Möglichkeiten zur
Definition von Funktionen. Einige GCL-Kommandos verlangen komplexe Argumente als Eingabe, geometrische Objekte in verschiedenen Formaten. Für diese OOGL-Objekte (object oriented
graphics library) gibt es dann jeweils eine spezielle Syntax, die auch gleichzeitig als Dateiformat
dient. Geomview kennt OOGL-Formate für folgende Objekte:
Vierecksliste, Streckenzug, reguläres Vierecksnetz, Bezierfläche, BRep, Kugel
Transformierte Objektinstanz, Listen (Subszene), transformierte Subszene
Transformation, Kamera, Fenster
Es existiert eine ganze Reihe von Modulen, Demonstrationen wie Rotationen im Vierdimensionalen vorzustellen sind, wie der hyperbolische Raum durch ein rechtwinkliges Dodekaeder
aufgeteilt (tesseliert) werden kann, oder die Visualisierung einer einzugebenden komplexen Funktion f : C ! C . Eine Visualisierung zeigt ein Polygon im hyperbolischen Raum, läßt eine Kante
selektieren und rotiert eine Kopie des Objektes um diese Kante, so kann man durch “Entfalten”
modellieren. Daneben gibt es verschiedene Module, die aufgerufen werden können, wenn bereits
eine Geometrie besteht, die man also als Werkzeuge auffassen kann. Es gibt Module zum Auftrennen von BReps in mehrere Schichten, zum Bemalen von Objekten im Dreidimensionalen, zur
Stereosicht in zwei Fenstern, und ein Modul, das zum Aufzeichnen von Animationen dient.
Anwendungsprogramme haben natürlich die Möglichkeit, zusätzliche Fenster zu öffnen, in
denen die anwendungsrelevanten Parameter vom Benutzer verändert werden können. Geomview
kann von einem Anwendungsprogramm somit als geometry engine benutzt werden, zum Beispiel
zum Modellieren: Ein Modeller schickt anfänglich eine Szenenbeschreibung an Geomview, öffnet
eine Werkzeugleiste, schickt dann bei Werkzeuganwendung ein Gizmo in die Szene, bearbeitet
Picking und Dragging-Ereignisse für Handles der 3D-Sicht, und so entsteht iterativ ein Objekt,
eine Szene.
In gewisser Weise kommt diese Trennung auch der Konzeption der Generativen Modellierung entgegen, wie Snyder sie mit GENMOD vorgeschlagen hat. Man kann die Ausdruckskraft
KAPITEL 3. GRUNDLAGEN
40
eines Modellieransatzes nach der Mächtigkeit der Schnittstelle zwischen Benutzerinteraktion und
geometry engine beurteilen. Geomviews GCL ist eine Sprache, die für einfachste Steueraufgaben entworfen worden ist, entsprechend enthalten Dateien sämtlicher OOGL-Formate auch nur
statische, approximative Beschreibungen von Objekten, seien sie drei- oder vierdimensional, mögen sie im euklidischen oder hyperbolischen Raum leben. Entscheidend ist jedoch, daß hier der
Weg beschritten wurde, die Verwaltung der Ansicht einem zentralen Modul zu übergeben, mit
dem über eine gegebene Sprache kommuniziert werden kann, die auch als Dateiformat funktioniert. Stellt man sich eine echte geometrische Programmiersprache wie GENMOD als Sprache für
eine solche Schnittstelle vor, so reduziert sich der Entwurf eines Generativen Modellers auf ein
Anwendungsprogramm, das mächtig genug ist, den Benutzer Programme eingeben zu lassen, und
auf eine geometry engine, die in der Lage ist, diese zu interpretieren und die konstruierten Modelle
darzustellen, sowie Interaktion mit der Darstellung an die Anwendung weiterzuleiten.
3.5
TBag
Das TBag-System [ESYAE95] ist ein Softwarepaket, das es gestattet, interaktive 3D-Anwendungen in C++ zu schreiben. Das API von TBag, der ’geometry layer’, realisiert dabei eine funktionale
Programmierschnittstelle, deren Ziel es ist, die Definition einer Geometrie einerseits extrem einfach und verständlich zu machen, andererseits eine Reihe von Optimierungen vornehmen zu können, um dem darunterliegenden TBag die Anwendung effizienter Algorithmen zu ermöglichen.
In TBag wird unterschieden zwischen Anwendungsprogrammierern, die den geometry layer
benutzen, und Programmierern, die das System erweitern möchten. Diesen wird die zugrundeliegende Klassenstruktur offenbart, um Vererbungsmechanismen benutzen zu können, Anwendungsprogrammierer benutzen lediglich die Basisklassen, durch überladene Operatoren ist eine
sehr knappe Schreibweise möglich. Darüberhinaus wird dem Anwender die Konstruktion neuer
Objekte durch Wrapper-Funktionen abgenommen, sodaß der Code alleine aus Funktionsaufrufen
und Zuweisung an Referenz-Variablen besteht. Einige Basisobjekte sind bereits instantiiert, sodaß
Code der folgenden Form möglich ist:
extern Geometry& cube;
extern Geometry& sphere;
extern Color& yellow;
Geometry& block_with_ball(Transform& xform, Color& color)
{
return ( cube*scale(1,4,1)*color
+ sphere*xlt(0,2,0)*yellow) ) * xform;
}
In dieser Funktion werden die überladenen Operatoren * und + verwendet, um einem Objekt
vom Typ Geometry ein Attribut zu verleihen oder eine Transformation anzuwenden, und um zwei
Szenen zu einer Gesamtszene zu verbinden, wobei auch ein einzelnes Objekt bereits eine Szene
ist. – Weiter verfügt das TBag-System über einige Algorithmen zum ’constraint solving’, die es ermöglichen, einerseits ’constrainables’ in Template-Schreibweise zu definieren, andererseits durch
’assertions’ vorzuschreiben, daß bestimmte constraints stets einzuhalten sind.
Constrainable<Transform&> trans1, trans2;
assert( trans1*trans2 == identity_trans );
Constrainable<Geometry&> geo = block_with_ball(trans1,red)+
block_with_ball(trans2,blue) ;
assert( trans1 == rot(y_axis,Time)*xlt(sin(Time),0,0) );
3.6. ACTIVEVRML
41
Damit werden zwei Transformationen definiert, wobei die eine immer die Inverse der anderen sein soll, eine Geometrie aus transformierten gelben Bällen mit Quadern, einer rot und einer
blau, und schließlich soll die eine Transformation stets eine Komposition einer Rotation und einer
Translation sein, beide zeitabhängig.
Diese prägnante Schreibweise ist jedoch nur möglich, weil Attribuierung und Subszenenbau
nicht auf die extern deklarierten Objekte cube und sphere selber wirken, sondern auf Kopien,
die von den Operatoren vor Anwendung der Attribute angelegt werden. Daher führt der geometry layer von TBag die Beschränkung der Wertkonstanz ein, einmal konstruierte Objekte werden
nicht mehr verändert. Einerseits bedeutet diese Restriktion, daß bei naiver Implementation erhebliche Kosten dadurch entstehen, daß stets bei jedem Ausdruck neue Objekte angelegt werden, andererseits ermöglicht es dem darunterliegenden TBag-System, constraints in effizienter Weise zu
lösen, weil es an den Ausdrücken Optimierungen vornehmen kann, die nicht möglich wären, wenn
sich Teile der Ausdrücke möglicherweise nachher ändern würden. Man hat daher Seiteneffekte
ausgeschlossen, muß allerdings mehr Aufwand bei der Speicherverwaltung in Kauf nehmen.
Betrachtet man die zugrundeliegende Klassenhierarchie von Geometrien, Transformationen
und Attributen sowie Hilfsklassen wie Farben, Punkten und Vektoren, so ist auffällig, daß die
Funktionalität nicht über abstrakte Basisklassen und virtuelle Funktionen realisiert wird. Dies ist
aber konsistent mit der Sichtweise von Objekten als konstanten Werten, die nicht mehr verändert
werden. Zudem wurde es von den Entwicklern von TBag als nicht zumutbar erachtet, Basisklassen
später um weitere Methoden zu erweitern, wenn alle Objekte einer Hierarchie eine zusätzliche
Fähigkeit bekommen sollen. Stattdessen wurde daher ein Mechanismus der Programmiersprache
CLOS verwandt, das multiple dispatching. Dabei werden Methodentabellen konstruiert, die für
jede Funktionalität vorsehen, wie welche Klasse auf eine Anfrage einer bestimmten Signatur zu
reagieren hat. Macht man eine Memberfunktion einer Klasse zu einer externen Funktion, so erhält
diese ein zusätzliches Argument, die Instanz der Klasse. Daher lassen sich Memberfunktionen wie
normale Funktionen in eine Tabelle einfügen. Das Zufügen einer neuen Funktionalität zu einer
Klassenhierarchie beschränkt sich damit auf das Anlegen einer neuen Tabelle.
Damit wird es möglich, etwa bei Einführung einer neuen Grafikbibliothek sämtliche bestehenden Objekte an die neuen Rendering-Fähigkeiten anzupassen, ohne die Klassen selber zu berühren.
3.6
ActiveVRML
Ein gravierender Nachteil von TBag ist, daß Anwendungsprogrammierer gezwungen sind, ihre
Szenen durch einen Präprozessor bearbeiten zu lassen, einen C++-Compiler zu starten, zu linken,
um schließlich festzustellen, daß einige Objekte die falsche Größe besitzen. Es besteht daher nach
wie vor die Notwendigkeit, eine geometrische Programmiersprache zu finden, die die Ausdruckskraft von TBag mit einem interpretierten Ansatz verbindet.
Ein interessanter Vorschlag für die Nachfolgeversion 2.0 des erwähnten VRML 1.0 wurde von
C. Elliott, einem der Entwickler von TBag, für die Firma Microsoft eingereicht. Dort hat man
von einer Abwärtskompatibilität zu VRML 1.0 abgesehen und stattdessen eine funktional orientierte Programmiersprache als Beschreibungssprache vorgeschlagen, ActiveVRML [Ell96]. Dies
geschah allerdings weniger im Hinblick auf geometrische Modellierung als vielmehr, weil man
auf diese Weise wie in TBag Gegenständen sehr bequem Attribute verleihen kann und sich Szenengraphen mit Ereignissen kombinieren lassen. Die Syntax von ActiveVRML ist an StandardML angelehnt, eine funktionale Standardsprache und Vorläufer der in Kapitel vier vorgestellten
Sprache Haskell. Dort werden auch die Konsequenzen des funktionalen Paradigmas für die Modellierung eingehend dargelegt. – Ein wesentliches Prinzip von ML wie auch von TBag ist auch
KAPITEL 3. GRUNDLAGEN
42
hier verwirklicht, die Wertkonstanz: Objekte können konstruiert, aber nicht verändert werden. Ein
Beispiel sind zwei hüpfende Bälle:
ball
= readGeometry("sphere.wrl");
ballHeight = (9-(time-3)^2) until bounceEvent => ballHeight;
motion(y)
= translate(time,y,ballHeight);
bounceEvent = predicate(ballHeight < 0);
newBall(y) = transformGeometry(motion(y),ball);
ball1 = newBall(1.4);
ball2 = newBall(2.2);
pairOfBalls = beachBall union superBall;
Im Prädikat ballHeight wird ein Verhalten definiert, ein oder mehrere Folgepfeile stehen für
die Reaktion auf ein eintretendes Ereignis. Es startet dabei mit einer eigenen Zeit time, die bei
Konstruktion auf 0.0 gesetzt wird; kommt es zu einem bounceEvent, so wird das alte ballHeigt
durch ein neues mit neuer Zeit ersetzt. Die Bewegung motion wird davon jedoch nicht beeinflußt,
ihre Zeit läuft weiter, der Ball hüpft also in x-Richtung ins Unendliche. Bemerkenswert ist die
Parametrisierbarkeit der Konstruktion des hüpfenden Balles, daher lassen sich Instanzen durch
Funktionsaufrufe konstruieren, schließlich entsteht eine zusammengesetzte Geometrie.
Bei der Konzeption von ActiveVRML standen Animationsaspekte und Benutzerinteraktion
klar im Vordergrund. Es gibt folgende Kategorien von Objekten:
Grundtypen: Zahlen, Boole’sche Werte, Funktionsdefinition, Paare, Listen
Modelliertypen: Farbe, 2D- und 3D-Vektoren, Bitmaps, Einlesen von Geometrien, Lichtquellen, Tonquellen, Kameras, Mikrophone
Interaktion: Ereignisse, Verhalten, Picking
In einer detaillierten Besprechung wird auf einer – mittlerweile gelöschten – WWW-Seite von
Microsoft ein Vergleich gezogen zwischen ActiveVRML und dem zwischenzeitlich verabschiedeten VRML 2.0-Standard, der auf eine Erweiterung von VRML 1.0 hinausläuft. Dabei werden
ActiveVRML-Beispielen solche in einer prozeduralen Sprache gegenübergestellt, eine Programmierweise ähnlich wie bei OpenInventor wird dort ’scene graph style’ genannt. – Als Vorteile von
ActiveVRML werden in dieser Besprechung folgende Punkte geltend gemacht:
Das Sprachkonzept ist rein funktional-deklarativ und damit konsequenter.
Konstante Ausdrücke lassen sich besser optimieren.
Aufbau statischer Szenen mit prozeduralen Programmen ist das falsche Konzept.
Verhalten und Ereignisse lassen die gleiche Interaktivität wie Ereignisschleifen zu.
Die Möglichkeit, geometrische Modellierung in ActiveVRML selber auszuführen, ist bedauerlicherweise nicht als Vorteil genannt worden, obwohl die Beispiele in Kapitel sechs auch ähnlich
in ActiveVRML zu realisieren gewesen wären.
Leider wird auf die formalen Eigenschaften von ActiveVRML in der Dokumentation überhaupt nicht eingegangen, es ist nicht klar, wie das verwendete Typsystem aussieht, ob Haskellähnliche Klassen existieren, geschweige, daß eine Grammatik der Sprache angegeben würde. Es
gibt auch keine Hinweise auf eine Referenzmplementation, insbesondere werden Effizienzaspekte nicht besprochen. – Zudem erfordert die Organisation der Benutzerinteraktion in funktionalen
Sprachen generell ein Umdenken im Vergleich zur Ereignis-Schleife prozeduraler Programme,
man bemüht dort Konzepte wie Monaden oder ’continuation based I/O’ [Hud89], wo Übergangsfunktionen benutzt werden.
Die Weiterentwicklung dieses Ansatzes ist von Microsoft nach der Ablehnung von ActiveVRML als neuem Standard offenbar sofort eingestellt worden.
Kapitel 4
Generative Modellierung
Nach der Analyse von Modellieransätzen sollen nun in zweiten Teil der Arbeit zwei Möglichkeiten vorgestellt werden, Generative Modellierung zu implementieren. Es werden dabei die beiden
Ansätze verfolgt, die in der Analyse hergeleitet worden sind. Zum einen soll innerhalb des objektorientierten Paradigmas der Ansatz der Metaobjekte verfolgt werden, andererseits wird eine
Implementierung in einer rein funktionalen Sprache vorgestellt. Dabei wird im besonderen darauf eingegangen, welche Fähigkeiten funtionaler Sprachen für die Modellierung besonders von
Wichtigkeit sind, und in welcher Richtung die bereits recht umfangreiche C++-Implementation
erweitert werden muß, um ein vollwertiges Modelliersystem zu liefern.
Bei diesen Implementationen geht es allerdings zunächst nicht darum, einen kompletten Modeller zu bauen. Eine notwendige Vorbedingung dazu ist vielmehr, ein Konzept für die innere
Organisation eines Generativen Modellers zu entwickeln, ein Modell, das es erlaubt, die geforderte Flexibilität im der Konstruktion von Gegenständen tatsächlich zu realisieren. Es wird in diesem
Fall also der umgekehrte Weg beschritten: Statt einen interaktiven Modeller zu bauen und diesem
mit einem Dateiformat zu versehen, um Modelle zu speichern und abzurufen, soll hier zuerst zum
einen der innere Aufbau und zum anderen die passende Form für eine geometrische Programmiersprache definiert werden, um einem interaktive Programm gezielt die notwendigen Fähigkeiten
verleihen zu können. Der vielversprechendste Ansatz für einen generativen Modeller ist eine hybride Darstellung von 3D-Modell und generiertem Programmtext, denn die Haskell-Beispiele
in 6.2 geben einen Eindruck von den Einflußmöglichkeiten, die ein Anwender notwendigerweise
haben muß.
Neben den Zielen Änderbarkeit und Wiederverwendbarkeit von konstruierten Objekten wird
dabei ebenso die Erweiterbarkeit des Modells auf programmierspachlicher Ebene im objektorientierten Sinn verfolgt. Beide vorzustellenden Konzeptionen bewegen sich dabei auf unterschiedlichen Ebenen und sollen getrennt voneinander behandelt werden, auch wenn die gleiche Idee
zugrundeliegt.
Die im Abschnitt 2.3 gegebene Definition von Oberflächen als Patchkomplexe ist jedoch in
beiden Modellen nicht realisiert, obwohl viele der Beispielkonstruktionen in Kapitel 6 zusammenhängende Flächenkomplexe zum Ergebnis haben. Zunächst einmal ist jedoch nicht klar, wie
die Idee des Modellierens mit Abbildungen, die natürlicherweise im Raum der Freiformflächen
lebt, auf Objekte mit einer zugrundeliegenden diskreten Struktur wie Patchkomplexe übertragen
werden kann. Daher geht es an dieser Stelle zunächst darum, wie Kurven und Flächen durch die
Kombination elementarer Operatoren realisiert werden können. — Patchkomplexe sind jedoch die
Richtschnur, an der man sich auch in diesem Bereich langfristig zu orientieren hat.
43
KAPITEL 4. GENERATIVE MODELLIERUNG
44
4.1 Generative Modellierung in C++
Grundlage für die C++-Implementation ist eine Einbindung in die MRT-Plattform. Wie bereits
erläutert, dient der MRT im wesentlichen zum Rendern von statischen Szenen, die über Skriptdateien eingelesen werden.
Eine erste Erweiterung in Richtung Modellierung wäre das Einlesen von Objektbeschreibungsdateien, die zum Beispiel das Resultat eines Modellierprozesses enthalten. Das ist so vorzustellen,
als würde man die Aktionen des Benutzers bei der Modellierung mit einem interaktiven Modeller
mitschneiden und diese Aktionen in einer Skriptdatei speichern, die von einem Renderer wie dem
MRT wieder eingelesen werden kann, um die modellierten Objekte darzustellen. Dieser Ansatz
würde im wesentlichen der Herangehensweise von Snyder entsprechen. Es bliebe dabei unbenommen, dies später zu einem interaktiven Modeller auszubauen, indem die Modellieraktionen nicht
aus einer Skriptdatei gelesen werden, sondern Benutzerinteraktionen zu einem Modell verarbeitet. Für die gemeinsame Schnittstelle von Skriptdatei und Interaktion zum Rendering-Modul kann
man sich einen Mechanismus vorstellen, der ähnlich ist wie der zum Einlesen von msd-Dateien
verwendete. Man hätte also als Rückgabewert des Parsens eine Reihe modellierter Objekte in
einem Szenengraphen, ebenso wie bei der Schnittstelle von interaktiver Modellierung und Rendering. Umgekehrt gilt, daß man beide Möglichkeiten der Eingabe von modellierten Objekten,
Parser und Benutzerinteraktion, nachträglich anfügen kann, wenn erst einmal die Schnittstelle in
Form einer Klassenhierarchie für die Generative Modellierung spezifiziert ist.
Die Ausdruckskraft eines Modellierparadigmas wird aber vornehmlich von der internen Organisation eines Modellers bestimmt und erst in zweiter Linie von den Eingabemöglichkeiten, denn
jedes Modell muß diese Schnittstelle zum Rendering passieren. Es geht bei einer Implementierung der Generativen Modellierung also vordringlich darum, eine entsprechende Klassenstruktur
zu entwerfen.
Was man zusätzlich noch erreichen möchte, ist die Erweiterbarkeit einer solchen Klassenstruktur. Es soll also nicht jeweils ein fester Typ für Zahlen, Vektoren, Kurven und Flächen vorgegeben
werden, sondern die Objekte, mit denen generativ modelliert wird, sollen austausch- und erweiterbar sein. Das bedeutet, daß zunächst ein ganz abstrakter Kern von Klassen implementiert wird, auf
dem der Rest des Paketes beruht, und außerhalb definierte Klassen durch sogenannte WrapperKlassen in das System aufgenommen werden können.
4.1.1
Abbildungen als Objekte
In Verallgemeinerung der Modifier, die bei 3D-Studio Max in einem Stack gehalten werden, wird
beim Genmod-Paket, der Erweiterung des MRT zum Generativen Modellieren innerhalb des bestehenden objektorientierten Paradigmas – wie in 2.5 auf Seite 23 ausgeführt –, mit Abbildungsobjekten gearbeitet. Grundlage von Genmod bilden zwei Basisklassen für Metaobjekte, zum
einen die Basisklasse t_GmMAP für Abbildungsobjekte oder Maps (für englisch “Abbildung”),
zum anderen für abzubildende Objekte die Variablen-Basisklasse t_GmVar. Die Ableitungen von
t_GmVar heißen Variablenklassen, das sind die eigentlichen Objekte, mit denen modelliert wird,
zum Beispiel also Zahlen und Vektoren. Daneben gibt es eine Kurven- und Flächenbibliothek,
das cns-Paket (für ’curves and surfaces’), das als Beispiel einer Genmod-externen Klassenhierarchie durch Wrapper in das Genmod-Paket integriert wurde. Dieses Paket sowie die Realisierung
der Einbindung wird im Kapitel 5.1 über die Einzelheiten der C++-Implementation im einzelnen
vorgestellt.
Hat man nun beispielsweise eine Real-Klasse als Ableitung der Variablenbasisklasse und die
arithmetischen Grundoperationen durch Ableitungen der Abbildungs-Basisklasse realisiert, so
4.1. GENERATIVE MODELLIERUNG IN C++
45
wird der Ausdruck (3 + 4) + 5 durch fünf Objekte repräsentiert, drei Konstanten, modelliert als
konstante Abbildungen, die den Rückgabetyp Real haben, sowie zwei Addier-Abbildungen, die
als Eingabe zwei und als Ausgabe einen Real-Wert besitzen.
Um die Typsicherheit zu gewährleisten, hat jede Abbildung eine Signatur, das ist eine Liste
von Typ-Identifiern, die angeben, von welchem Typ die Werte sind, die von der Abbildung verarbeitet werden, und was zurückgegeben wird. Jedes Abbildungsobjekt legt lokal eine Rückgabevariable an, in der der Wert nach seiner Berechnung gespeichert wird. Ihrer Signatur entsprechend
erhält eine Abbildung ihre Eingaben von Rückgabewerten anderer Abbildungen, enthält also Zeiger auf Eingabeabbildungen.
Damit kann man eine Kette von Abbildungen aufbauen, wo die erste Abbildung einen Eingabewert verarbeitet, aus diesem einen neuen Wert berechnet, dieser ist die Eingabe der nächsten
Abbildung, bis schließlich an der letzten Abbildung der Wert der Berechnungskette ausgelesen
werden kann. Am Anfang der Kette stehen dabei konstante Abbildungen, die selber keine Eingaben brauchen und einen festen Rückgabewert haben. – Haben einzelne Abbildungen dabei mehr
als eine Eingabe, so wird aus dieser Kette ein Berechnungsbaum, wie in dem Beispiel (3+4)+5.
Zusätzlich soll gestattet sein, einzelne Zwischenergebnisse dabei mehrfach zu verwenden, sodaß bei (3+4)+5+2*(3+4) der Ausdruck (3+4) nur einmal ausgewertet werden muß. Auf diese
Weise wird aus dem Berechnungsbaum ein gerichteter azyklischer Graph, ein DAG (directed acyclic graph). Um eine solche verkettete Abbildung auszuführen, wird von der zuletzt auszuführenden Abbildung eine Auswertungskaskade in Gang gesetzt, weil zuerst die Eingabedaten dieser
Abbildung berechnet werden müssen, indem die Eingabeabbildungen ausgewertet werden. Auf
diese Weise wird der Auswertungsprozeß also durch den gesamten DAG weiterpropagiert, und
die Ergebnisse werden in der umgekehrten Richtung durchgereicht.
Const_0
Op_0
Const_1
Op_1
Const_2
Zu diesem Zweck bietet die Basisklasse t_GmMAP zwei Funktionen an, die am Auswertungsprozeß beteiligt sind, eine public-Funktion execute() und eine rein virtuelle protected-Funktion
evaluate(), die von jeder abgeleiteten Klasse überschrieben werden muß. Um eine gegebene gebundene Map map auszuführen, kann ein Benutzer map.execute() aufrufen. Diese Funktion ruft
ihrerseits ein execute() für sämtliche Eingabeabbildungen auf und danach die eigene Memberfunktion evaluate(), die damit alle Rückgabewerte der ausgewerteten Eingabeabbildungen benutzen kann. Das bedeutet, execute()-Nachrichten werden vom Ende zum Anfang des DAGs gereicht,
dann werden die Ergebnisse von evaluate() vom Anfang zum Ende hin verarbeitet.
Ein DAG entspricht aber einer partiellen Ordnung. Wählt man eine totale Ordnung, die mit
dieser partiellen Ordnung verträglich ist, so erhält man eine Sequenz von Abbildungen, die nur
einmal nacheinander ausgeführt werden müssen, und es ist sichergestellt, daß die Eingaben jeder
einzelnen Abbildung bereits berechnet sind. Das hat den Vorteil, daß Abbildungen nicht mehrfach
ausgewertet werden wie Const_1 im Beispiel.
Const_2
Const_1
Const_0
Op_0
Op_1
KAPITEL 4. GENERATIVE MODELLIERUNG
46
Um Abbildungen in einem Programm zu verwenden, muß nun ein Interface zwischen normalen C++-Variablen oder -Konstanten des Programms und den Ein- und Ausgabevariablen der Abbildung geschaffen werden. Dies wird im Genmod-Paket über konstante Abbildungen erreicht,
die bei Konstruktion einen Zeiger auf einen C++-Wert oder eine C++-Konstante erhalten, die in
ihre Rückgabevariable übertragen wird. Gleichzeitig bietet jede Abbildung eine Möglichkeit, den
augenblicklichen Wert der Rückgabevariablen zu erfragen, sodaß man das Endergebnis der Berechnung an der letzten Abbildung auslesen kann. Diese zusammengesetzte Abbildung kann nun
jedesmal neu ausgeführt werden, wenn sich die variablen C++-Eingabewerte ändern.
Entscheidend für die Flexibilität dieses Ansatzes ist nun die Tatsache, daß Abbildungsobjekte
zur Laufzeit des Programmes angelegt und miteinander verbunden werden können. So erreicht man
ein ähnliches Verhalten wie bei einem Interpreter, wo Programmfragmente interaktiv eingegeben
und überprüft werden können. Der Zweck dieses Generativen Modells ist aber die ’Berechnung’
von geometrischen Objekten, nicht die Ausführung eines allgemeinen Algorithmus’. Programme,
die ohne Schleifen und if-Abfragen auskommen, nennt man auch ’straight-line-programs’, dies ist
gerade die Klasse der hiermit realisierten Programme. Ähnlich wie in interaktiven Modellern werden Operationen damit entweder ausgeführt, oder sie werden nicht ausgeführt, ohne daß zunächst
Verzweigungen notwendig wären.
Ein straight-line-program, ein linearisierter DAG von Abbildungen, kann auch einzelne Abbildungen enthalten, die noch nicht vollständig gebunden sind, wo also Parameter noch nicht
mit Eingabeabbildungen verbunden wurden. Eine solche Kette teilgebundener Abbildungen kann
dann als eigenständiges Abbildungsobjekt behandelt werden, als ein Makro oder eine Gruppe von
Abbildungen, die als solche Eingaben erhält, die an die ungebundenen Parameter weitergegeben
werden. Die Signatur der zusammengesetzten Abbildung setzt sich damit aus Verweisen auf die
ungebundenen Variablen der Einzelabbildungen zusammen.
Op_0
Const_1
Op_1
Eine zusammengesetzte Abbildung dieser Art wird damit als Einheit behandelt, die in anderen Abbildungen verwendet werden kann, und die mittels ’deep copy’ vervielfältigbar ist. Es
handelt sich unter anderem also um eine Kopiervorlage, ein Objekt im Speicher, das als Teil einer speicherinternen Werkzeugbibliothek aufbewahrt werden kann, um es kopieren und flexibel
verwenden zu können.
Hat man eine C++-Bibliothek von Modellierobjekten, Zahlen, Vektoren usw., so kann man
diese in Variablen- und Abbildungsklassen verpacken, um damit generativ zu modellieren. Entscheidend ist dabei, daß eine solche Einbindung in Genmod flexibel und einfach bewerkstelligt
werden kann. Der Genmod-Kern enthält daher neben der Variablen- und der Abbildungsbasisklasse und der zusammengesetzten Abbildung noch eine Variablen-Templateklasse, die einfach eine
Instanz des übergebenen Typs enthält. Sind Variablenklassen durch Template-Instanziierung definiert, so kann man Abbildungen definieren. Dazu gibt es noch zwei Templates für konstante Abbildungen, die zusammen mit der Einführung eines Variablentyps instantiiert werden. Zum einen
ist dies eine Abbildung, die bei Konstruktion einen Verweis auf eine Programmvariable erhält, aus
der der Wert jeweils ausgelesen wird, und zum anderen eine Abbildung, die eine Konstante vom
Template-Parametertyp enthält, die, einmal gesetzt, den Wert nicht mehr ändert.
Der Kern des Genmod-Paketes besteht somit aus einer sehr überschaubaren Menge von Klassen und Templates, deren Funktionsweise, bis auf die Teile, die für den Anwender notwendig sind,
4.1. GENERATIVE MODELLIERUNG IN C++
47
nach außen hin verborgen bleiben kann.
Zudem ist die Abbildungs-Basisklasse so gemacht, daß eine Ableitung von ihr, die eine spezielle Abbildung implementiert, für den Benutzer sehr einfach zu schreiben ist, weil die gesamte
Verwaltung von Signaturen und Rückgabewerten in Basisklassenfunktionen geschieht, sodaß ein
Anwendungsprogrammierer nicht mit Verwaltungsaufgaben belastet wird. Vielmehr hat die Klasse
t_GmMAP im wesentlichen zwei virtuelle protected-Funktionen, die vom Anwender überschrieben werden müssen. Das ist einmal fillSignature() zum Setzen der Signatur der Abbildung, damit
das Binden typsicher abläuft und der Variablentyp einer Rückgabevariablen mit dem erwarteteten Typ übereinstimmt, der in der Signatur vereinbart wurde. Zudem wird dabei sichergestellt,
daß keine un- oder teilgebundenen Abbildungen ausgewertet werden können. Zum anderen wird
evaluate() überschrieben, die Funktion, in der die eigentliche Arbeit geleistet wird.
Um zum Beispiel in Genmod das Kreuzprodukt zweier Vektoren bilden zu können, das durch
den überladenen Multiplikationsoperator für Objekte vom Typ t_3DVector gegeben ist, definiert
man eine Abbildungsklasse nach folgendem Schema:
class t_GmMapPt3DCross : public t_GmMAP
{
...
protected:
virtual t_Bool evaluate () ;
virtual t_Bool fillSignature () ;
private:
t_3DVector* a;
t_3DVector* b;
t_3DVector* c;
};
t_Bool t_GmMapPt3DCross::fillSignature ()
{
signature(t_GmVar3DVectorPtr(a))(t_GmVar3DVectorPtr(b))
(t_GmVar3DVectorPtr(c));
return True;
}
t_Bool t_GmMapPt3DCross::evaluate()
{
(*a)=(*b)*(*c);
return True;
}
Der Benutzer muß sich dabei nicht um das richtige Setzen der Zeiger kümmern, weil dies
beim Binding durch die Basisklasse erledigt wird. – Diese Vorgehensweise funktioniert für alle
Memberfunktionen von Variablenklassen in Genmod, die gewisse Voraussetzungen erfüllen:
Jede Memberfunktion einer Variablenklasse, die – explizit oder implizit – nur einen Rückgabewert hat, kann in einer Genmod-Abbildung gekapselt werden.
Für straight-line-programs solcher Funktionen läßt sich eine Normalform finden, in der C++Statements und Funktionsaufrufe dargestellt werden können. Dazu werden sämtliche Ausdrücke
aufgelöst und explizit Variablen angelegt.
KAPITEL 4. GENERATIVE MODELLIERUNG
48
So wird aus diesem Programmfragment:
Class1 a;
Class4 d=some_function(a,Class2(),a.member_function(Class2());
durch Normalisierung das folgende Programmfragment:
Class1
Class2
Class3
Class4
a;
b;
c=a.member_function(b);
d=some_function(a,b,c);
Das bedeutet, C++-Programme, die nur aus Konstruktoren und Aufrufen von Funktionen bestehen, die keine destruktiven Updates von Objekten durchführen, lassen sich auf diese Weise
linearisieren. Im Unterschied zu destruktiven gibt es auch nicht-destruktive Updates, die keine Information ändern und damit löschen, sondern Information hinzufügen, beispielsweise das Anhängen an eine Liste in LISP. — Für solche C++-Programme gilt dann, daß sie sich – falls die nötigen
Wrapper eingebracht wurden – als zusammengesetzte Genmod-Abbildung, mithin in einem DAG
darstellen lassen. Dabei werden in jeder Zeile neue Variablen eingeführt, und alle Statements, die
danach folgen, dürfen auf diese Variable zugreifen, und diese Variable hat stets denselben Wert.
Der Grund für das Verbot destruktiver Updates ist, daß Seiteneffekte ausgeschlossen werden
sollen, die durch eine Verwendung derselben Variablen mit verschiedenen Werten eintreten können. Genmod-Abbildungen haben nur einen einzelnen Rückgabewert. Hat eine Memberfunktion,
oder eine Funktion allgemein, mehrere Rückgabewerte, so kann nur einer davon von der Abbildung zurückgegeben werden. In Genmod gibt es nun aber keine Zeilennummern, keinen Hinweis
darauf, in welcher Reihenfolge Funktionen abgearbeitet werden müssen, die Auswertungsreihenfolge wird allein durch die Propagierung der Evaluierung der Eingabewerte ausgehend von der
Ergebnisabbildung eines DAGs bestimmt. Anders ausgedrückt, die Reihenfolge der Statements in
einem Programm ist nur eine mögliche totale Ordnung, die zu dem gerichteten azyklischen Graphen der Aufrufabhängigkeiten paßt. Es ist in Genmod aber nicht definiert, welche totale Ordnung
bei Linearisierung einer zusammengesetzten Abbildung gewählt wird.
Mehrere Rückgabewerte besitzt beispielsweise eine Funktion, die als Parameter eine Referenz oder einen Zeiger auf ein weiteres Objekt erhält, das von der Funktion modifiziert wird.
Insbesondere gilt dies für Memberfunktionen, die ihre Objektinstanz verändern, zum Beispiel setFunktionen, mit denen der innere Zustand eines Objektes verändert wird, die aber auch selber
einen Wert zurückliefern, meist vom Typ Boolean. Memberfunktionen erhalten nämlich implizit
einen weiteren Parameter, und zwar die Objektinstanz, zu der sie gehören. Solche Funktionen lassen sich nur schlecht in Genmod integrieren, denn damit das funktioniert, muß das veränderte Objekt in den Rückgabewert der Abbildung kopiert werden, und jede Veränderung erzeugt ein neues
Objekt. Dies ist zu verschmerzen, wenn man beispielsweise 3D-Vektoren normalisieren möchte,
der Rückgabewert der Abbildung ist eine normalisierte Kopie des Eingabevektors. Unpassend sind
jedoch Funktionen, die ein destruktives Update von großen Datenstrukturen durchführen. Hat man
eine Memberfunktion zum Verschieben einer Ecke eines Dreiecksnetzes, so wird daraus eine Abbildung, die ein Dreiecksnetz, eine Ecke des Netzes und einen Verschiebungsvektor erwartet und
ein neues Dreiecksnetz zurückliefert; als Rückgabewert der Abbildung wird aber eine Kopie des
gesamten Netzes mit einem verschobenen Knoten angelegt.
4.1. GENERATIVE MODELLIERUNG IN C++
4.1.1.1
49
Integration von Kurven und Flächen
Eine zusammengesetzte Abbildung mit der Signatur (Real,Real) ! Vector kann als parametrische
Fläche aufgefaßt werden.
Um das angestrebte Modellieren mit Abbildungen zu realisieren, ist daher ein spezielles Konzept von Kurven- und Flächenobjekten notwendig. Und zwar sollten die entsprechenden Klassen
über eine parametrische Auswertungsfunktion verfügen, das bedeutet für Kurven, zu einem gegebenen t einen Punkt p auf der Kurve und für eine Fläche, zu (u; v) ein p auf der Fläche zu liefern.
Das cns-Paket enthält daher eine Klassenhierarchie, die im wesentlichen auf drei Basisklassen aufbaut. Diese Klassen t_CurveParametric3D, t_SurfaceParam3D und t_Function3D haben virtuelle
Memberfunktionen der folgenden Form:
t_3DVector& t_CurveParametric3D::evaluate (t_Real t)
t_3DVector& t_SurfaceParam3D
::evaluate (t_Real s, t_Real t)
t_3DVector& t_Funct3D
::evaluate (const t_3DVector& p)
Teil der Integration der Kurven- und Flächenbibliothek sind daher eine Kurven- und eine Flächenklasse, deren Konstruktoren Genmod-Abbildungen mit einer passenden Signatur erhalten,
und deren überladene evaluate()-Funktionen nichts anderes machen, als ihre Abbildung auszuwerten. Auf diese Weise lassen sich also zusammengesetzte Abbildungen wiederum als Kurven
und Flächen auffassen, aus denen dann wieder neue Abbildungen erzeugt werden können.
Neben dieser Basisfähigkeit verfügen die Basisklassen über Funktionen, um differentialgeometrische Größen wie Tangente, Normale und Binormale einer Kurve, das Frenet’sche Dreibein,
und die partiellen Ableitungen und die Normale einer Fläche an einer Stelle zu berechnen. Eine
Abbildung zum Frenet’schen Dreibein findet man in Kapitel 5 auf Seite 66.
Betrachtet man etwa die Memberfunktion zur Tangentenbestimmung der Kurvenklasse, so
kann man diese in eine Abbildung verpacken, die als Eingabe eine Kurve und einen Parameter
erhält und als Rückgabewert einen Vektor liefert. Auf diese Weise lassen sich Abbildungen für alle
entsprechenden Memberfunktionen konstruieren, die dann der Werkzeugbibliothek als elementare
Operatoren hinzugefügt werden.
Die Verbindung zum MRT geschieht nun folgendermaßen. Die Kurven- und Flächenbibliothek
ist durch Variablenobjekte in Genmod integriert, insbesondere sind die Kurven- und die Flächenbasisklasse gültige Genmod-Variablen. Nun gibt es im Genmod-Paket Abbildungen t_GmMapCurveMap und t_GmMapSurfaceMap, die eine zusammengesetzte Abbildung passender Signatur
erwarten, und nicht nur deren Rückgabewert benutzen, sondern die gesamte Abbildung kopieren.
Die kopierte Abbildung wird dann dem Konstruktor der abgeleiteten Kurvenklasse t_CurveMap
bzw. der Flächenklasse t_SurfaceMap übergeben. Dort wird ein Rebinding der Eingabeparameter
durchgeführt, sodaß die Memberfunktion evaluate auf die Auswertung der zusammengesetzten
Abbildung umgeleitet wird. Diese Kurve oder Fläche ist dann der Rückgabewert der GenmodAbbildungen t_GmMapCurveMap bzw. t_GmMapSurfaceMap.
Für parametrische Kurven- und Flächenobjekte gibt es wiederum – in Snyders Terminologie –
eine Methode, die eine Umwandlung in ein anzeigbares Objekt vornimmt. Dies ist einmal eine von
t_Object abgeleitet Klasse, deren Konstruktor eine parametrische Kurve bekommt und diese als
einen Schlauch darstellt, denn Kurven haben als eindimensionale Punktmenge keine darstellbare
Fläche. Parallel dazu gibt es eine Klasse, die eine parametrische Fläche erwartet und diese zum
approximativen Rendering in ein anzeigbares BRep umwandelt, indem ein Sampling der Fläche
auf einem regelmäßigen Gitter im Parameterraum durchgeführt wird.
Zu jeder darzustellenden Genmod-Abbildung wird also ein parametrisches Objekt erzeugt,
welches dann durch Sampling in ein MRT-Objekt umgewandelt wird, das in eine Szene eingebaut,
angezeigt und interaktiv betrachtet und verfeinert werden kann.
KAPITEL 4. GENERATIVE MODELLIERUNG
50
4.1.2
Vergleich des Genmod-Paketes mit GENMOD
Das Zusammensetzen von Abbildungen geschieht bei Snyders GENMOD durch das Schreiben
und Interpretieren von Metashapes, das sind Funktionen, die MAN-Objekte erhalten, neue aus
ihnen erzeugen und ein MAN-Objekt zurückgeben. In ähnlicher Weise kann man Genmod benutzen, um C++-Funktionen schreiben, die aus Abbildungen neue Abbildungen machen. Allerdings ist man durch diese Arbeitsweise beim Modellieren auf die Metashapes angewiesen, die
zur Compilezeit im System vorhanden sind, das bedeutet, ein interaktiver Interpreter ist nicht Teil
des Genmod-Paketes. Es ist eher auf eine einfache Benutzungsweise und ein verständliches API
für die Abbildungen selber Wert gelegt worden, um Anwendungsprogrammierern, die Metashapes entwerfen, die Arbeit zu erleichtern. Zudem sind Abbildungen auf diese Weise zu Objekten
geworden, die zur Laufzeit erzeugt und verbunden werden können. Auf diese Weise ist erst die
Voraussetzung geschaffen worden, die grundsätzliche Beschränkung auf festverdrahtete Werkzeuge eines Modellers einmal zu überschreiten. Skriptdateien und Interaktionen, die in welcher
Weise auch immer Vorschriften zur Konstruktion von Abbildungen beschreiben, können nun zur
Laufzeit eines Programmes ausgeführt werden und so parametrisierte dreidimensionale Modelle
zusammensetzen, die interaktiv angezeigt werden.
Ein grundsätzlicher Unterschied zwischen dem MAN-Datentyp von GENMOD und dem Abbildungskonzept von Genmod ist der, daß ein MAN stets eine Abbildung von Rn ! Rm ist, also
zwischen reellen Vektorräumen. GENMOD besitzt als Programmiersprache aber auch die anderen Grunddatentypen wie Zahlen, Vektoren und Zeichenketten, die man bei der Programmierung
verwenden kann. – In sofern ist das Genmod-Paket allgemeiner, weil die Art der verwendeten Datenstrukturen nur durch die Beschränkung auf Funktionen ohne destruktive Updates begrenzt ist.
Andererseits beraubt man sich durch diese Allgemeinheit der Möglichkeit, Inklusionsfunktionen
für sämtliche Abbildungen definieren zu können, diese rekursive Operatormethode von GENMOD
fällt also weg. Das läßt sich zunächst damit rechtfertigen, daß das Thema von Gemod mehr die
Modellierung an sich ist als Intervallarithmetik und das Lösen impliziter Gleichungssysteme, zu
dem diese dient. Andererseits ist das Problem des Umganges mit impliziten Gleichungen durchaus
ein Kernthema der Modellierung, verdient aber eine gesonderte Behandlung an anderer Stelle und
wurde in Genmod daher ausgeklammert.
Grundsätzlich besteht allerdings die Möglichkeit, rekursive Operatormethoden wie die Bestimmung der Ableitung oder der Inklusionsfunktion auch in Genmod durchzuführen. Dazu wären
folgende beiden Punkte notwendig:
Operatormethoden müssen Memberfunktionen sein
Bei der Integration von Kurven und Flächen in Genmod wird die Operatormethode Auswertung für diese Objekte realisiert. Das gelingt dadurch, daß die cns-Klassen über evaluate()Memberfunktionen verfügen, um Parameter auszuwerten. – Um effizient eine Inklusionsfunktion für eine Bézierfläche zu finden, müßte die Bézierklasse eine entsprechende Memberfunktion zur Verfügung stellen, weil von außen kein Zugriff auf die internen Berechnungen der Klasse bei der Auswertung eines Flächenpunktes möglich ist: Nur die Bézierklasse
selber kann bestimmen, wie weit ihre Fläche bei einem gegebenem Urbildintervall variiert.
Jede Operatormethode muß für jeden elementaren Operator existieren
Dieser Punkt ist von entscheidender Komplexität: Setzt man eine Fläche aus elementaren
arithmetischen Ausdrücken zusammen und versucht dann, die Inklusionsfunktion dieser
Fläche zu bestimmen, so muß dies rekursiv erfolgen. Das bedeutet, daß ähnlich wie in Snyders Implementation alle elementaren Operatoren um eine solche Methode ergänzt werden
müssen. Ähnliches gilt für die Berechnung von Ableitungen als Operatormethode.
4.1. GENERATIVE MODELLIERUNG IN C++
51
Der C++-Implementation von Genmod sind aber durch ihre Allgemeinheit Grenzen gesetzt,
denn es gibt schwächere Voraussetzungen für Operatoren. Insbesondere sind beispielsweise BReps
ein Variablentyp, der im Genmod-Paket enthalten ist, und für eine Abbildung, die eine Liste von
Punkten in einen BRep verwandelt, macht die Berechnung einer Ableitung keinen Sinn.
Um den Unterschied zwischen GENMOD und dem Genmod-Paket in bezug auf die Modellierung weiter zu charakterisieren, kann man feststellen, daß in Genmod die Abbildungsklassen
implementiert sind, aus denen zusammengesetzte Abbildungen zusammengesetzt werden können.
Was allerdings fehlt, ist ein Konzept für den Zusammenbau von Operatoren. Snyder benutzt dazu
C-Funktionen, die Abbildungen erhalten und Abbildungen zurückgeben. Die zusammengesetzten
Abbildungen selber sind der Inhalt des Genmod-Paketes, aber es gibt es keine Objekte, um vorzuschreiben, welche Abbildungen wie zusammengesetzt werden. In diesem Sinne sind Operatoren
noch eine weitere Indirektion, die dazu dient, eine Vorschrift zu kodieren, wie eine Abbildungen
zusammenzubauen ist. Inhalt des Genmod-Paketes ist ein flexibler Mechanismus, um Abbildungsobjekte zu definieren und zu Metashapes zusammenzusetzen, aber keine Klassen zur Definition,
wie Metashapes zusammengesetzt werden sollen.
Um diesen Unterschied zu verdeutlichen, kann man etwa den Plus-Operator in GENMOD betrachten, der dazu dient, zwei MAN-Abbildungen a und b, deren Resultat Vektoren sind, zu einer
neuen Abbildung c zusammenzusetzen, deren Resultat die Summe dieser Vektoren ist. In Genmod
lautet die entsprechende Zeile MAN c=a+b. Der +-Operator ist in diesem Fall also ein Metaobjekt,
eine Funktion, wenn man so will, die eine zusammengesetzte Abbildung zurückliefert. Die Implementation für diesen Operator in Genmod lautet unter Benutzung von Reference-Pointern wie in
[Fis95a] beschrieben daher:
t_GmMapPt3DAddPtr add_vector(t_GmMAP& p, t_GmMAP& q)
{
t_GmMapPt3DAddPtr s = new t_GmMapPt3DAdd;
(*s)(p)(q);
return s;
}
Diese Funktion ist damit die Implementation eines elementaren Operators, letztendlich nur ein
weiterer Wrapper zum Binden der Abbildungen, die die Implementation des Operators darstellt.
Ein solcher Wrapper könnte direkt von einem Parser aufgerufen werden, der den Ausdruck a+b
verarbeitet, und feststellt, daß irgendwelche zwei Abbildungen addiert werden, die 3D-Vektoren
zurückliefern. Dieser Ausdruck wird dabei in der Definition eines höheren Operators benutzt, und
man hätte die gleiche Funktionalität wie bei Snyder. Um ein Metashape zu definieren, das die
Strecke s pq (t ) = (1 , t ) p + tq zwischen zwei Punkten p; q als eine Abbildung mit der Signatur
Real! Vector realisiert, könnte der Code auf C++-Ebene folgendermaßen aussehen. Dabei
verwendet man der Deutlichkeit halber noch keine überladenen C++-Operatoren, obwohl damit
der Code noch GENMOD-ähnlicher werden würde.
t_GmMapRealAdd interpolate(t_GmMAP& t, t_GmMAP& p, t_GmMAP& q)
{
t_GmMapRealMinus one_minus_t=real_minus(real_const(1.0),t);
return add_vector(scale_vector(one_minus_t,p),scale_vector(t,q));
}
Genmod besitzt jedoch keine solchen Operatoren, und zwar aus dem einfachen Grunde, daß
man nicht mehr an das Programmieren von Abbildungen in C++ gebunden sein möchte. Vielmehr
geht es in der zweiten Implementation von Genmod in Haskell darum, ein Sprachkonzept zu erarbeiten, das der generativen Idee besser angepaßt ist als das von Snyder verwendete imperative
KAPITEL 4. GENERATIVE MODELLIERUNG
52
Sprachparadigma. In einer funktionalen Sprache kann einfach auf einer höheren Ebene modelliert
werden.
Genmod bietet also zwar keine Objekte, um Vorschriften für den Zusammenbau von Abbildungen zu formulieren, also keine Operatorfunktionen wie GENMOD, aber das Binden von Abbildungen kann – wie im Code zu add_vector() – mit Hilfe des ()-Operators durchgeführt werden,
der in der Abbildungs-Basisklasse t_GmMAP definiert ist. Damit können auf C++-Ebene Operatoren programmiert werden, es müssen allerdings – wie in der Normalform für C++-Programme
– sämtliche Statements einzeln behandelt werden, und dabei führt man stets neue Variablen ein,
was zuweilen etwas unübersichtlich werden kann. Der Zweck von Genmod ist wie gesagt aber
nicht vordringlich die leichte Definition von Operatoren in C++, sondern Laufzeitflexibilität. Die
wirkliche Definition von interpolate() sieht in Genmod daher folgendermaßen aus:
t_GmMapCompose interpolate(t_GmMAP& t,
{
t_Real realOne=1.0;
t_GmMapRealConst
one(&realOne);
t_GmMapRealMinus
one_m_t;
t_GmMapPt3DScalMult scale_p;
t_GmMapPt3DScalMult scale_q;
t_GmMapPt3DAdd
add_p_q;
t_GmMapCompose
segment;
t_GmMAP& p, t_GmMAP& q)
one_m_t
scale_p
scale_q
add_p_q
segment
(one)(t);
(one_m_t)(p);
(t)(q);
(scale_p)(scale_q);
(add_p_q);
return segment;
}
Für die zusammengesetzte Abbildung segment werden dabei die einzelnen Terme konstruiert, zunächst wird (1 , t ) berechnet, dann die beiden Vektoren skaliert, und diese schließlich
summiert. Die zusammengesetzte Abbildung t_GmMapCompose linearisiert den DAG, es wird
schließlich nur ein einziges Abbildungsobjekt zurückgegeben, das eine Sequenz von Abbildungen enthält, die von t_GmMapCompose als Kopien der automatischen Variablen angelegt worden
sind, die ja mit Beendigung der Funktion – und vor allem nach Konstruktion des Rückgabewertes
segment – wieder vom C++-Stack entfernt werden.
Nun ist die Schreibweise im zweiten Beispiel zwar deutlich mühsamer und bewegt sich auf
einem niedrigeren Level, andererseits ist es mit einer Einführung von Wrapper-Funktionen, die es
ermöglichen, Funktionen wie im ersten Beispiel zu schreiben, eben nicht getan. Die Realisierung
höherer Operatoren, von Metashapes also, durch C++-Funktionen, ist denkbar unbefriedigend,
weil diese Funktionen compiliert werden, also nicht, wie angestrebt, während des Programmlaufes
definiert werden können. Was stattdessen mit Genmod realisiert wurde, ist folglich der zugrundeliegende Kern einer Sprache:
Erweiterbare Menge von “eingebauten” Datentypen
Erweiterbare Menge von elementaren Operationen auf diesen Datentypen
Damit hat man aber noch keine höheren Konstrukte, die zu einer interpretierten Programmiersprache gehören. Der erste Schritt zur Erweiterung des Mechanismus wäre die Einführung eines
Funktionsobjektes, das ist eine Bindevorschrift für eine Sequenz von Abbildungen. Diese müßte
das gleiche leisten wie obige Funktion interpolate(). Das heißt, man kann flexibel vorschreiben, wo
die Eingabe-Maps t, p und q an andere Maps gebunden werden. In Genmod wurde dazu mit speziellen Aufrufabbildungen experimentiert, die ein Binding bzw. Rebinding durchführen können.
Das bedeutet, eine Kopiervorlage, ein Werkzeug, wird zunächst kopiert, und dann mit neuen Parametern versehen. – Das bedeutet aber letztendlich auch, Abbildungen zu Objekten zu machen, die
4.1. GENERATIVE MODELLIERUNG IN C++
53
das Objekt von Abbildungen sind. An dieser Stelle findet sich aber eine prinzipielle Grenze, denn
die Memberfunktionen der Abbildungsbasisklasse führen destruktive Updates durch und können
daher selber nicht in Abbildungen verpackt werden. An dieser Stelle wird deutlich, daß es nötig
ist, tatsächlich eine neue Meta-Ebene oberhalb von Abbildungen einzuführen, die den Umgang
mit Abbildungen in objektorientierter Weise realisiert.
Um Genmod zu einer vollen Programmiersprache auszubauen, wäre es daher nötig, die folgenden Punkte zu realisieren:
Funktionsobjekte
Ein Funktionsobjekt ist eine Bindevorschrift, die es ermöglicht, eine Instanz eines Werkzeuges zu erzeugen und an spezielle Parameter zu binden. Es führt sämtliche Aktionen durch,
die beim Aufruf obiger C++-Funktion interpolate ausgeführt werden.
Kontrollstrukturen
Um Strukturen zu realisieren wie eine Liste von Objekten, deren Parameter variiert werden,
sind Schleifen oder – grundlegender – Verzweigungen und relationale Ausdrücke notwendig. Grundlage hierfür wäre eine branching-Abbildung wie in GENMOD
Aufrufsemantik
Es muß ein Stack-Konzept gefunden werden, wenn Funktionsaufrufe objektorientiert gekapselt sind, um “lokale Variablen” zu speichern. Dabei ist nicht klar, wann der Stack wieder
zu löschen ist, weil keine Objekte verlorengehen dürfen, die noch referenziert werden. Es
wäre dazu ein Mechanismus denkbar, der wie Reference-Objects [Fis95a] für Abbildungen
funktioniert.
Scoping
Es muß ein Konzept für lokale Variablen bzw. lokale Definitionen von Funktionsobjekten gefunden werden, um temporäre Ausdrücke darstellen zu können. Dies ist wichtig, um
die Flut von C++-Variablen bzw. Genmod-Variablenobjekten einzudämmen, die durch die
Rückführung auf die objektorientierte Normalform entstanden ist.
Die Mächtigkeit der Funktionsobjekte, die erforderlich sind, läßt sich aber an den umfangreichen Programmen ablesen, die bereits in Genmod realisiert sind, siehe die Beispiele in 6.1.
Der Punkt der Aufrufsemantik ist dabei von besonderer Bedeutung. Bisher wird so vorgegangen, daß ein DAG von Abbildungen aufgebaut wird, dieser wird in einer zusammengesetzten
Abbildung linearisiert. Das bedeutet, die Klasse t_GmMapCompose verfolgt die Eingaben einer
übergebenen Abbildung zurück, bis sie auf konstante Abbildungen stößt, die keine weiteren Eingaben mehr benötigen, oder auf eine Abbildung, die der t_GmMapCompose explizit als Eingabeabbildung mitgeteilt wurde. Dazu wird der ()-Operator benutzt. – Alle entdeckten Abbildungen
werden kopiert und die Kopien neu verbunden, die entstehende Sequenz kann in einem Durchgang abgearbeitet werden. Ist nun aber eine Teilabbildung dabei selber eine zusammengesetzte
Abbildung, so wird diese bei der Kopie selber vollständig kopiert. Das bedeutet, wenn komplizierte Abbildungen über mehrere Stufen zusammengesetzt werden, so besteht die resultierende
Abbildung aus einer unter Umständen sehr langen Sequenz von Elementaroperatoren.
Aus diesem Grunde ist bei der gegenwärtigen Implementierung keine Aufrufsemantik vonnöten, weil stets mit einer ’deep copy’ einer Abbildung gearbeitet wird, die lokal gebunden ist und
deren Bindung auch nicht wieder gelöst wird. Der Grund für diese Vorgehensweise sind Effizienzüberlegungen, denn das Linearisieren und Binden ist wegen der Typsicherheit ein verhältnismäßig aufwendiger Prozeß, der eben nicht zur Compilezeit, sondern zur Laufzeit stattfinden muß,
KAPITEL 4. GENERATIVE MODELLIERUNG
54
dann, wenn Abbildungen verbunden werden. Um die Auswertungszeit einer zusammengesetzten
Abbildung gering zu halten, wird also nur einmal gebunden. Dadurch haben zusammengesetzte
Abbildungen in Genmod eine rasend hohe Ausführungsgeschwindigkeit, es bestehen praktisch
keine Verwaltungs-Overheads, und die Rechenzeit ist allein die Rechenzeit für die jeweiligen
Elementarabbildungen, die der Anwender voll unter Kontrolle hat, weil er sie selber schreibt.
Eine kurze Ausführungszeit ist aber zum Beispiel beim Sampling von entscheidender Bedeutung,
wenn die Abbildung, die eine Fläche parametrisiert, mehrere hundert Mal mit verschiedenen (u; v)Werten ausgewertet wird.
Folgende Entsprechung gilt damit zwischen dem Genmod-Paket und der GENMOD-Sprache.
Genmod-Paket
Variablenklassen t_GmVar
Abbildungsklassen t_GmMAP
(C++-Funktionen)
(C++-Funktionen)
execute()
Umwandlung in MRT-Objekt
!
!
!
!
!
!
GENMOD-Programmiersprache
reelle Vektoren
Mannigfaltigkeit MAN
elementare Operatoren
Metashapes
Operatormethode Auswertung
Operatormethode Sampling
Statt jedoch Genmod zu einer vollen Programmiersprache auszubauen, wurde zunächst das
bestehende Paket untersucht, indem einige Standardbeispiele der Generativen Abbildung zu Testzwecken in Normalform überführt wurden, zusammen mit den nötigen Operatoren in Form von
C++-Funktionen. Einige dieser Beispiele, die eine konkrete Vorstellung vom Nutzen der Generativen Modellierung vermitteln, werden in 6.1 besprochen.
4.2 Generative Modellierung in Haskell
Um sich dem Problem zu nähern, welche Sprachklasse angemessen ist, um eine geometrische
Programmiersprache für die Generative Modellierung zu realisieren, wurde eine Referenzimplementation im funktionalen Paradigma vorgenommen, weil dort das Prinzip des Rechnens mit
Abbildungen am konsequentesten verfolgt wird. Dabei wurde deutlich, daß einige der Probleme,
auf die man bei der Realisierung von Genmod gestoßen ist, prinzipielle Probleme der Generativen
Modellierung sind, für die in funktionalen Sprachen zum Teil Lösungen existieren.
Beispielsweise läßt sich argumentieren, daß die Wiederverwendbarkeit eines Werkzeugs genauso wie die Referenzierbarkeit eines Modells an verschiedenen Stellen in einer Szene Wertkonstanz von Variablen bzw. von Rückgabewerten von Abbildungen impliziert. Dies wiederum
bedeutet, daß man zum Beispiel keine Schleifen wie in imperativen Sprachen schreiben kann,
denn die Schleifenvariable verändert sich; die funktionale Programmierung beantwortet dies so,
daß Rekursion anstelle von Schleifen verwendet wird. Die inhärente Ineffizienz der Rekursion
im Vergleich zur Schleife kann in bestimmten Fällen aber dadurch aufgehoben werden, daß ein
Compiler einer funktionalen Sprache erkennt, daß eine ’tail recursion’ intern wie eine Schleife
behandelt wird und entprechend effizienter abläuft.
Dies ist ein Beispiel, wo kontrolliert destruktive Updates, in diesem Falle der Schleifenvariablen, durchgeführt werden können, und im Bereich der Forschung auf dem Gebiet der funktionalen
Sprachen werden komplizierte Algorithmen entwickelt, um Möglichkeiten für solche Optimierungen zu finden.
Ein entscheidender anderer Punkt, der Beachtung verdient, ist das Fehlen höherer Funktionen
sowohl in Snyders GENMOD-Ansatz als auch zunächst im Genmod-Paket. Das bedeutet, es ist in
GENMOD keine Möglichkeit vorgesehen, Metashapes auszutauschen. Wird in einer Konstruktion
der wire-product-Operator m_wire(MAN cross, MAN wire) wie in Abschnitt 3.1 definiert verwandt,
4.2. GENERATIVE MODELLIERUNG IN HASKELL
55
so steht der entsprechende Funktionsaufruf fest im Programmtext. Es ist nicht möglich, den Operator selber zu einem Parameter eines anderen Operators zu machen, das bedeutet, eine Konstruktion
kann nicht in Hinsicht auf die verwendeten Operatoren parametrisiert werden. Um dies zu leisten,
wäre in GENMOD eine weitere Indirektion erforderlich, eine Erweiterung des Abbildungsbegriffs
von Funktionen Rm ! Rn auf Funktionen mit Signaturen wie
surface_deformation: ( R2 ! R3 ) ! ( R2 ! R3 )
aus Abschnitt 2.5.2 mit einem entsprechend angepaßten Konzept von Operatormethoden. Das
bedeutet aber gerade den Übergang zu höheren Funktionen, die aber ebenfalls ein Konzept aus
funktionalen Sprachen darstellen. Der Nutzen solcher höherer Funktionen wird in in der HaskellImplementation dadurch deutlich, daß man einen einheitlichen Beschreibungsrahmen für Objekte,
für Meta-Objekte, die auf Objekten operieren usw. formulieren kann. Dadurch kann es Klassen
geben, die eine Funktion als Parameter erwarten, und aus ihr ein Flächenobjekt machen, ganz
ähnlich wie t_GmMapSurfaceMap.
Andererseits ist es möglich, daß es auch nicht nötig ist, eine komplette funktionale Sprache zu
realisieren, um generativ modellieren zu können. Generatives Modellieren ist offensichtlich auch
in einem C-Derivat wie GENMOD möglich, doch die damit realisierten Programme sind wie das
Beispiel in 3.1 sehr schematisch aufgebaut und scheinen dem Sprachumfang von C nicht angemessen. Dies kann als ein Indiz dafür angesehen werden, daß ein anderes Sprachkonzept sinnvoller
wäre. Um herauszufinden, in welcher Weise funktionale Konzepte bereits in Genmod teilimplementiert sind, und um herauszuarbeiten, welche weiteren Konzepte funktionaler Sprachen in der
Modellierung von Nutzen sein können, scheint es gerechtfertigt, dem Genmod-Paket eine Implementation in einer funktionalen Sprache gegenüberzustellen.
4.2.1
Warum Haskell?
Neben einer Implementation in C++ kann die Genmod-Idee ebenso im Rahmen einer funktionalen Programmiersprache formuliert werden. Dies bedeutet keine Aufgabe der objektorientierten
Sichtweise; vielmehr ist Anfang der neunziger Jahre eine Synthese beider Konzepte in Form der
Programmiersprache Haskell entstanden, die eine neuere Entwicklung im Bereich der funktionalen Programmierung darstellt.
Haskell vereint eine ganze Reihe von Konzepten, die von verschiedenen funktionalen Programmiersprachen eingeführt worden sind. Auf einer Konferenz über funktionale Sprachen, der
FPCA ’87 in Portland, Oregon, wurde von Sprachentwicklern beklagt, daß zwar viele Konzepte
im Laufe der Jahre entwickelt und auch realisiert worden sind, daß aber ein Mangel an Einheitlichkeit herrscht, der die Forschung eher behindert als weiterbringt. Daher wurde dort Haskell, benannt
nach Curry B. Haskell, einem führenden Logiker, als eine Sprache entworfen, die Einzelentwicklungen der funktionalen Programmierung in sich vereint und Objektorientierung als zusätzliches
Konstrukt einführt. Haskell kann also als die funktionale Standardsprache angesehen werden und
ist aus diesem Grund für eine funktionale Implementation ausgewählt worden.
Haskell zeichnet sich durch die folgenden Merkmale aus [Hud89], [HJe92], deren Bedeutung
für die Modellierung anschließend erläutert wird.
Funktionen höherer Ordnung
Funktionen sind ’first class citizens’ und können an Funktionen als Parameter übergeben,
zusammengesetzt sowie als Rückgabewerte zurückgegeben werden. Curried functions sind
teilgebundene Funktionen, ist add eine Funktion Float!Float!Float, so ist (add 5.0) eine
Funktion Float!Float und (add 5.0 2.0) eine Zahl. Lambda-Ausdrücke sind anonyme, also
unbenannte Funktionen, ( (n x y -> x+y ) 5 7 ) ist 12.
KAPITEL 4. GENERATIVE MODELLIERUNG
56
Lazy evaluation
Im Unterschied zum ’call by value’-Mechanismus prozeduraler Sprachen, bei dem Funktionsargumente vor Übergabe ausgewertet werden, wird bei ’call by need’ lazy evaluation
eingesetzt. Dabei werden Argumente erst dann ausgewertet, wenn sie innerhalb einer Funktion auch tatsächlich benutzt werden. Dies hat den Vorteil, daß virtuell unendliche Datenstrukturen verwendet werden können.
Statisches Typsystem
Sämtliche Variablen und Funktionen sind statisch getypt, wodurch Typfehler bereits bei
der Kompilation erkannt werden. Zum Typsystem gehören dabei auch Typvariablen sowie
beliebig komplexe Funktionstypen.
Polymorphismus
Haskell unterstützt ad-hoc-Polymorphismus, Überladen, wo Funktionen gleichen Namens
für unterschiedliche Typen unterschiedliche Wirkung haben, und parametrischen Polymorphismus, wo generische Funktionen mit Hilfe von Typvariablen geschrieben werden, sodaß
über alle Typen hinweg die gleiche Funktionalität realisiert wird. Dies ist zu vergleichen
mit C++, wo beides über virtuelle Funktionen und den Template-Mechanismus realisiert
ist. Beispiel ist eine Funktion twice, die eine Funktion f zweimal hintereinander anwendet,
(twice f) ist dann vom gleichen Typ wie (f):
twice f x = f (f x)
Pattern matching
Funktionen können je nachdem, wie die übergebenen Variablen aussehen, durch Pattern
matching verschiedenes Verhalten zeigen.
List comprehensions
Es gibt einen sehr flexiblen Mechanismus, wie aus Listen neue Listen erzeugt werden können, der an die mathematische Schreibweise angelehnt ist. Eine quasi-unendliche Liste aller
Fibonaccizahlen erhält so man mit zip, das aus zwei Listen eine Liste von Paaren macht:
fib = 1:1:[ a+b | (a,b) <- zip fib (tail fib) ]
Rekursion
Die Fibonaccizahlen sind ein Beispiel einer rekursiven Definition. In funktionalen Sprachen
gibt es keine Schleifen; daher benutzt man stattdessen rekursive Definitionen, die deshalb
von Compilern auch entsprechend effizient unterstützt werden.
Klassen
Haskell-Klassen sind lediglich Interface-Vereinbarungen, wo die Signaturen der Memberfunktionen definiert werden. Dies ist mit rein virtuellen Klassen von C++ vergleichbar. Ein
Datentyp wird zu einer Instanz einer Klasse, indem erklärt wird, wie die Funktionen der
Klasse auf diesen Datentyp wirken.
Referentielle Transparenz
Grundlage funktionaler Sprachen ist der Lambda-Kalkül. Dies ist ein reiner Ersetzungskalkül für typisierte Ausdrücke. Daraus leitet sich das funktionale Sprachparadigma ab: Equals
can be replaced by equals. Das bedeutet, daß nach einer Zuweisung eines Ausdrucks an
eine Variable an allen Stellen, wo diese Variable verwendet wird, genausogut der Ausdruck
eingesetzt werden könnte.
4.2. GENERATIVE MODELLIERUNG IN HASKELL
57
Der Eigenschaft der referentiellen Transparenz kommt dabei im Rahmen der Modellierung
eine besondere Bedeutung zu. Verwendet eine Haskell-Anweisung y=x+x eine Variable x und ist
diese definiert als x=f 1, so entspricht die Definition von y dem Ausdruck y=(f 1)+(f 1). In prozeduralen Sprachen ist dies nicht unbedingt der Fall, weil es sein kann, daß zwischen einer Zuweisung x=f(1) und der Verwendung in y=x+x der Variablen x ein neuer Wert zugewiesen wird.
Dieser Seiteneffekt ist in funktionalen Sprachen ausgeschlossen, weil eine zweite Zuweisung an
x vom Compiler als Fehler erkannt würde. Daher ist herkömmliches sequentielles Programmieren
in funktionalen Sprachen nicht möglich, und alle Definitionen, aus denen ein Programm besteht,
gelten gleichberechtigt nebeneinander als Gleichungen. – Ein bemerkenswerter Gewinn ist dabei,
daß funktionale Programme durch das Fehlen von Seiteneffekten wesentlich effizienter zu verifizieren sind als herkömmliche imperative.
Umgekehrt bedeutet referentielle Transparenz aber auch Wertkonstanz, denn Objekte, sind sie
einmal definiert, können ihren Wert nicht mehr ändern. Intern werden Objekte solange behalten,
wie sie noch referenziert werden. Solange können sie auch als Parameter für Funktionsaufrufe
eingesetzt werden. Diese Tatsache hat folgende Konsequenzen für die Modellierung.
Wertkonstanz ermöglicht erst Wiederverwendbarkeit und Änderbarkeit.
Jede Manipulation an einem Objekt erzeugt ein neues Objekt.
4.2.1.1
Referentielle Transparenz: Nutzen
Um die Bedeutung einer referentiellen Transparenz zu erläutern, ist es zunächst nützlich, festzustellen, daß dies einer unbegrenzten Undo-Fähigkeit gleichkommt. Denn dadurch, daß jede
Anwendung einer Operation auf ein Objekt ein neues Objekt erzeugt, wird keine Information
zerstört, und die ursprünglichen Objekte können stets an anderer Stelle erneut verwendet werden.
Zum einen ist damit also auf jeder Stufe eine Änderbarkeit gegeben, weil sämtliche Schritte des
Konstruktionsprozesses nachträglich noch zugreifbar sind. Andererseits können alle Zwischenergebnisse eines abgeschlossenen Konstruktionsprozesses nachträglich neu und anders verwandt
werden. Damit ist auf dieser Ebene eine Wiederverwendbarkeit aller Teilergebnisse der Konstruktion gegeben. Um ein Teilergebnis wiederzuverwenden, muß man es lediglich referenzieren, also
benennen, in einer Liste speichern oder einer übergeordneten Funktion als Rückgabewert zur Verfügung stellen.
4.2.1.2
Referentielle Transparenz: Kosten
In der Modellierung werden Objektstrukturen verwandt, die an Größe mehr und mehr zunehmen.
Ändert man aber nur die Position eines Punktes in einem Dreiecksgitter mit vielen tausend Einzeldreiecken, so fordert die referentielle Transparenz, daß ein neues Objekt anzulegen ist, das sich
an nur einer Stelle vom ursprünglichen Objekt unterscheidet. Und zwar ist dies nötig, wenn die
Möglichkeit besteht, daß das ursprüngliche Objekt noch an anderer Stelle verwendet, das bedeutet,
wenn es noch referenziert wird.
Die Entscheidung, einer Sprache die Eigenschaft der referentiellen Transparenz zu geben, hat
also weitreichende Konsequenzen. Die Unmöglichkeit destruktiver Updates, bei denen Speicherplatz bei Änderungen wiederverwendet werden kann, macht funktionale Programme sicher weniger effizient als sie es bei kontrollierter Wiederverwendung von Speicher wären. Dies ist einer
der Gründe dafür, warum funktionale Sprachen als hoffnungslos ineffizient und nicht tauglich für
reale Anwendungen gelten. Dies gilt wegen der Komplexität der Modelle in besonderer Weise für
58
KAPITEL 4. GENERATIVE MODELLIERUNG
die Modellierung, wo die populären Dreieckskomplexe leicht mehrere Megabytes groß werden
können.
Andererseits betreiben die Produzenten von Modellierpaketen auch einen nicht unerheblichen
Aufwand, um dem Benutzer möglichst viele ’Undo-Level’ anzubieten, und die Daten vor Änderung sind dabei auch zwischenzuspeichern. Ein typisches Beispiel, wie Benutzer mit diesem
Mangel umgehen, ist die Technik der ’Sicherungspunkte’, wo jeweils nach einer Reihe von Arbeitsschritten das komplette Modell, mit Versionsnummern, versehen “zur Sicherheit” gespeichert
wird, um später Zugriff auf die Ausgangselemente einer Konstruktion zu haben. – 3D-Studio Max
etwa stellt bei einem Verschneiden von BReps bei Booleschen Operationen die Frage, ob die Ausgangskörper gelöscht werden sollen. Selbst wenn diese Frage verneint wird, hat der Modellierer
aber keine Möglichkeit, im Modifier-Stack die ursprünglichen Elemente der Boole’schen Operation nachträglich zu modifizieren. Ein Objekt ’Boolean’ wird von 3D-Studio Max nämlich als ein
neues Objekt angesehen und nicht als Modifikation bestehender Objekte. – Diese Unterscheidung
wirkt künstlich und zeigt, wie beschränkt das Konzept des Modifier-Stack ist, wenn es in dieser
Weise implementiert wird. Schließlich ist nicht nur das Verbiegen eines Objektes eine Modifikation, die würdig ist, referenziert zu werden und die nachträglich änderbar sein sollte, sondern jede
Operation verändert ja die Szene als solche. Eine gesamte Szene hat aber keinen Modifier-Stack,
und nicht jede beliebige durchgeführte Aktion läßt sich benennen und an anderer Stelle in gleicher
Weise wiederholen. Würde ein Modeller dies alles anbieten, so wäre er de facto referentiell transparent und es ergäben sich die gleichen Effizienzprobleme wie jene, auf die die Forschung zur
Implementierung funktionaler Sprachen bereits seit längerem gestoßen ist. Dies ist ein weiterer
Beleg für die These, daß der Modellierung ein inhärent funktionales Paradigma zugrundeliegt.
Gleichzeitig wird hier aber auch ein Grundwiderspruch deutlich zwischen der objektorientierten Sichtweise wie sie in Sprachen wie Smalltalk oder C++ realisiert ist und der funktionalen
Sichtweise von Haskell-Klassen. C++ sieht ein Objekt als eine Datenstruktur, mit der über Nachrichten (Memberfunktionen) kommuniziert werden kann. Mit Hilfe von set- und get-Funktionen
können Informationen über seinen inneren Zustand (Membervariablen) ausgetauscht werden, dieser Zustand kann aber auch geändert werden, was die Möglichkeit eines destruktiven Updates
erfordert.
In Haskell sind Klassen aber nichts als Signaturvereinbarungen, über eine neue Klasse führt
man einen neuen Geltungsbereich für Memberfunktionen ein. Dann kann jeder Typ als zu dieser Klasse zugehörig erklärt werden indem man definiert, wie die nötigen Memberfunktionen der
Klasse auf diesem Typ operieren. So kann man etwa binäre Bäume zu Integers machen, wenn
nur die Grundrechenarten auf Bäumen erklärt werden, oder doppelt verkettete Listen zu Strings,
wenn man die Konkatenationsoperation definiert, ohne daß man nachher allerdings Zeichenketten und doppelt verkettete Listen miteinander konkatenieren könnte. Typen sind nämlich nur als
Implementation einer bestimmten Klasse anzusehen, nicht als Unterklassen einer gemeinsamen
Oberklasse. Haskell unterscheidet klar zwischen Klassen und Typen.
4.2.2
Formulierung von Genmod in Haskell
Die Haskell-Implementation von Genmod ist sehr knapp, alle nötigen Klassen finden in einer Datei von sechzehn Kilobytes Platz, siehe Anhang. Diese Referenzimplementation weist dabei starke
Ähnlichkeiten mit einer frühen C++-Implementation von Genmod auf und ist nicht mit so ausgefeilten Verwaltungs- und Erweiterungsmechanismen versehen wie das beschriebene GenmodPaket. Einerseits ist diese Realisierung daher mehr als ein ’proof of concept’ zu sehen, andererseits
erübrigen sich Optimierungen wie die Linearisierung eines DAGs von Abbildungen dadurch, daß
das Rechnen mit Abbildungen integraler Bestandteil von Haskell ist. Haskell verfügt als echte
4.2. GENERATIVE MODELLIERUNG IN HASKELL
59
Programmiersprache zudem über Scoping, lokale Funktionsdefinitionen und ein statisches Typsystem, um einige der bei Genmod notwendigen Erweiterungen zu nennen.
Die Implementation beschränkt sich auf die Basis-Typen für die Modellierung: Vektoren, Kurven und Flächen. Wie erwähnt werden dazu im wesentlichen die Signaturvereinbarungen benötigt,
die eine Haskell-Klasse ausmachen. Kurven und Flächen sowie Transformationen sind in einer
Weise definiert, daß nur die Auswertungsfunktion selber überschrieben werden muß, die anderen
Funktionen besitzen eine Default-Implementation, die auf dieser Auswertungsfunktion aufsetzt,
indem Ableitungen durch Differenzenquotienten bestimmt werden. Dieses Vorgehen ist vollkommen analog zur Vorgehensweise beim cns-Paket der Kurven und Flächen in Genmod.
class Curve c
tangent
mainNormal
biNormal
evalCurve
where
:: c ->
:: c ->
:: c ->
:: c ->
Float
Float
Float
Float
->
->
->
->
Vector3
Vector3
Vector3
Vector3
class Surface
tangentU
tangentV
normal
evalSurf
s where
:: s ->
:: s ->
:: s ->
:: s ->
Float
Float
Float
Float
->
->
->
->
Float
Float
Float
Float
->
->
->
->
Vector3
Vector3
Vector3
Vector3
class Transform t where
evalTrans :: t -> Vector3 -> Vector3
Diese Deklaration ist so zu lesen, daß eine Typ c eine Kurve ist, falls es eine Funktion evalCurve für diesen Typ gibt. Ist crv_0 eine Variable dieses Typs, so erfolgt die Auswerung durch
(evalCurve crv_0 0.5), dieser Ausdruck ist damit vom Typ Vector3.
Eine erste Version des Genmod-Paketes in C++, die ohne Trennung von Kern und cns-Bibliothek arbeitete, hat Klassen nach dem folgenden Muster verwandt, um etwa eine Kurve zu gewinnen, die zwischen zwei anderen Kurven liegt:
class CrvInterpol : public Curve
{
public:
CrvInterpol(Curve& c1, Curve& c2, Real t)
: crv1(c1), crv2(c2), t0(t) { }
virtual Pt3 eval(Real t)
{ return line(crv1.eval(t),crv2.eval(t),t0); }
private:
Curve& crv1;
Curve& crv2;
Real t0;
};
Diese Implementation wurde zugunsten einer Trennung von Genmod-Kern und geometrischer
Bibliothek aufgegeben, um eine Optimierung des Aufrufgraphen vornehmen zu können. – Die
Funktion line berechnet dabei (1 , t ) p + tq, die entsprechende Definition in Haskell benutzt ebenfalls eine Funktion line und sieht folgendermaßen aus:
line p q t = add (mult (1-t) p) (mult t q)
KAPITEL 4. GENERATIVE MODELLIERUNG
60
data (Curve c, Curve d) => CurveInterpol c d = CrvInterpol c d Float
instance (Curve c, Curve d) => Curve (CurveInterpol c d) where
evalCurve (CrvInterpol crv1 crv2 t0) t =
line (evalCurve crv1 t) (evalCurve crv2 t) t0
Das ist so zu lesen, daß mit der data-Deklaration ein neuer Typ eingeführt wird, ein Metashape,
und zwar CurveInterpol mit dem Konstruktor CrvInterpol1, der drei Argmente erwartet, nämlich
zwei Kurven und einen Interpolationsparameter. Um diesen neuen Typ zur Klasse Curve zugehörig zu erklären, wird in der instance-Deklaration nur definiert, was die Auswertungsfunktion
evalCurve tut. – CurveInterpol ist eine lineare Interpolation zwischen korrespondierenden Kurvenpunkten zweier Kurven. Für t0=0 entspricht sie crv1, für t0=1 crv2, und die Gesamtheit aller
Kurven für t0 aus [0; 1] entspricht der linearen Interpolationsfläche zwischen korrespondierenden
Kurvenpunkten. Diese Fläche kann unter Benutzung von CrvInterpol folgendermaßen definiert
werden:
data (Curve c, Curve d) => SurfInterpolCrv c d = SrfInterpolCrv c d
instance (Curve c, Curve d) => Surface (SurfInterpolCrv c d) where
evalSurf (SrfInterpolCrv crv1 crv2) u v =
evalCurve (CrvMorph crv1 crv2 u) v
Dies entspricht der Definition, die Snyder für die Generative Modellierung gegeben hat: Ein
Generatives Modell ist eine Form, die durch die kontinuierliche Transformation einer anderen
Form, des Generators, entsteht. In Haskell-Genmod gibt es nun eine ganze Reihe von Kurvenund Flächentypen, deren Instanzen wie CrvInterpol aus anderen Kurven und Flächen entstehen.
Darunter sind Reparametrisierung, Umkehrung der Parameterrichtung, Projektion einer Kurven in
den Parameterbereich einer Fläche, bilineare Coons-Interpolation von Randkurven zu einer Fläche
und weitere, die in der Implementation in Kapitel 5.2 vorgestellt werden, Beispiele dazu findet man
in 6.2.
Entscheidend bei der Benutzung von Haskell ist aber, daß man ebenfalls Kurven, Flächen und
Transformationen definieren kann, wenn man nur eine Funktion hat, deren Signatur der jeweiligen
Auswertungsfunktion entspricht. – Die allgemeinste Definition einer parametrischen Fläche ist
beispielsweise eine, wo einfach eine beliebige gegebene Funktion benutzt wird, die aus zwei FloatWerten einen Vektor macht. In Haskell sind dies Dreizeiler:
data SurfFct = SrfFct (Float->Float->Vector3)
instance Surface SurfFct where
evalSurf (SrfFct f) u v = f u v
data CurveFct = CrvFct (Float->Vector3)
instance Curve CurveFct where
evalCurve (CrvFct f) t = f t
data TransformFct = TrnFct (Vector3->Vector3)
instance Transform TransformFct where
evalTrans (TrnFct f) p = f p
Der Anwender modelliert nun, indem er diese vorgefertigten Kurven, Flächen und Transformationen benutzt und durch explizite Komposition zusammensetzt. Auf diese Weise erhält man
1 Namenskonvention: Typen
und Konstruktoren haben bis auf das Präfix identische Namen, Curve, Surface und
Transform sind die Präfixe für Typen, Crv, Srf und Trn jene für Konstruktoren
4.2. GENERATIVE MODELLIERUNG IN HASKELL
61
eine Trennung der Werkzeugbibliothek mit den Operatorklassen von der Anwendung. In der Anwendung, in der eine Szene mit ihren Gegenständen definiert wird, sieht man einmal die Kompositionsoperationen, die in einem interaktiven Modeller den Werkzeugaufrufen entsprechen, und
andererseits enthält eine Anwendung natürlich Konstanten, die den normalerweise interaktiv eingegebenen Punkten und Zahlen entsprechen. Andererseits ist es möglich, flexibel Konstanten, die
mehrfach auftreten, zu Parametern eines Modells zu erklären, sodaß man diese an zentraler Stelle ändern kann. Damit erübrigen sich Hilfskonstrukte wie Gitter, um auf bestimmte Punkte an
verschiedenen Stellen bezug nehmen zu können.
Andererseits hat der Benutzer die Möglichkeit, wiederkehrende Konstruktionen schließlich in
Klassen zu verpacken, die seine eigene Werkzeugbibliothek ergänzen. Dies soll an einem Beispiel verdeutlicht werden, das demonstriert, wie die eingeführten Klassen benutzt werden. Um
den Verlauf der Hauptnormalen einer Kurve zu zeigen, kann man diesen Richtungsvektor an jeder
Stelle der Kurve auftragen, also zum Kurvenpunkt hinzuaddieren. Um dies für eine Bézierkurve
mit bestimmten Kontrollpunkten durchzuführen, schreibt man die folgende Anwendung:
p0
p1
p2
p3
=
=
=
=
V3 (-2.0) 0.0 (-1.0)
V3 (-1.0) 0.8
0.0
V3
1.0 0.8
2.0
V3
2.0 0.0 (-1.0)
path
= CrvBezier p0 p1 p2 p3
pathNormal = CrvFct (\t -> add (evalCurve path t) (mainNormal path t))
srfNormal = SrfInterpolCrv path pathNormal
Statt ein spezielles Werkzeug zu verwenden, das zwei Kurven addiert, kann man so in Haskell
mit Hilfe einer anonymen Funktion t 7! path(t ) + pathnormal (t ) in Lambda-Schreibweise einfach
die Klasse CrvFct benutzen; das ist die entscheidende Flexibilität, die dieser Ansatz bietet. Zudem
ist es ganz einfach, eine “Haut” zwischen beide Kurven zu spannen, die Fläche SrfInterpolCrv
ist das Werkzeug, das sich dazu anbietet. – Benutzt man diese Konstruktion nun mehrfach in
verschiedenen Anwendungen, wobei man einmal den Verlauf der Hauptnormale, ein anderes Mal
den der Binormale oder der Tangente zeigen möchte, so bietet es sich an, aus dieser Konstruktion
ein Werkzeug zu machen, das der Anwenderbibliothek hinzugefügt wird.
data (Curve c)=> SurfOffsetIntp c = SrfOffsetIntp c (c->Float->Vector3)
instance (Curve c) => Surface (SurfOffsetIntp c) where
evalSurf (SrfOffsetIntp path fct) u v = evalSurf fctSrf u v
where fctSrf = SrfInterpolCrv path fctPath
fctPath = CrvFct (\t-> add (evalCurve path t) (fct path t))
Mit der zweiten where-Klausel werden hier lokale Variablen für den evalSurf-Ausdruck vereinbart, so werden in Haskell Mehrfachauswertungen von Termen vermieden.
Damit vereinfacht sich eine Anwendung, die Hauptnormale und Binormale zeigt, auf die folgenden beiden Zeilen:
srfBiNormal = SrfOffsetIntp path biNormal
srfNormal
= SrfOffsetIntp path mainNormal
An dieser Stelle wird die Bedeutung höherer Funktionen für die Modellierung deutlich: biNormal und mainNormal sind Memberfunktionen der Klasse Curve, die selber als Parameter der
Anwenderklasse SrfOffsetIntp dienen können. Gleichzeitig kann aber auch jede andere Funktion
KAPITEL 4. GENERATIVE MODELLIERUNG
62
benutzt werden, die aus einer Kurve und einem Parameter einen 3D-Vektor macht, um eine Instanz von SrfOffsetIntp zu erzeugen. Die innere Struktur dieser Klasse kann aber definiert werden,
ohne zu wissen, welche Funktion übergeben werden wird. Man hat also durch die Verwendung
höherer Funktionen bei der Generativen Modellierung die Möglichkeit, Konstruktionen auf einem
noch höheren Niveau zu beschreiben als dies in GENMOD möglich ist, wo zum Beispiel in der
Definition von m_wire in 3.1 der Aufruf von derivative nicht parametrisiert werden kann sondern
fest verdrahtet ist. Dies ist aber kein Mängel des Abbildungsbegriffs, sondern nur der speziellen
Implementierung in einem C-Derivat.
Um diese Kurven und Flächen nun anzuzeigen, existiert eine MRT-Anbindung eher provisorischer Natur auf dem Umweg über Dateien, die Kurven- und Flächenpunkte enthalten. Der MRT
wurde dazu um zwei t_SurfaceObjekt-Klassen ergänzt, die Samplingdaten aus Dateien lesen können. Die Operatormethode Sampling wird dazu in der main-Funktion von Haskell aufgerufen,
durch die die Auswertung der komponierten parametrischen Objekte angestoßen wird. Dabei kann
angegeben werden, in wieviele Einzelintervalle [0; 1] für Kurven und [0; 1] [0; 1] für Flächen aufgeteilt wird. Der Dateiname sollte dem Variablennamen entsprechen:
main = putStr $
++
(showCurve
"path"
(showSurface "srfNormal"
10
8 60
path)
srfNormal)
Der $-Operator von Haskell ist eine Abkürzung für die Klammerung sämtlicher folgender
Terme, f $ g x ist gleichbedeutend mit f(g x) und bedeutet f (g(x)). Im Gegensatz dazu steht f g x im
allgemeinen fehlerhafterweise für (f g) x, denn Funktionsapplikation ist in Haskell linksassoziativ.
– Kurven werden in Form von Schläuchen gerendert, deren Radius in der msd-Datei angegeben
werden kann. Die entsprechenden Zeilen lauten dann folgendermaßen:
inputcurve
0 "path"
(rad)
inputsurface 0 "srfNormal"
4.3
Zusammenfassung
Zum einen ist nun festzustellen, daß mit dieser minimalen Haskell-Referenzimplementation der
Generativen Modellierung mit kleinem Aufwand eine Ausdruckskraft erreicht wurde, die es ermöglicht, die Beispiele für Metashapes aus elementaren Operatoren in GENMOD direkt zu übertragen. Dazu soll noch einmal das ’wire product’ aus Abschnitt 3.1 betrachtet werden:
MAN m_wire(MAN cross, MAN wire)
{
MAN that = m_derivative(wire,wire->input_list[0]);
MAN t = m_normalize(that);
MAN n = @(-t[1], t[0]);
return @(n*cross[0]+wire, cross[1]);
}
MAN cross = m_crv("cross.crv",m_x(0));
MAN wire = m_crv("wire.crv", m_x(1));
MAN racket_frame = m_wire(cross,wire);
Das Resultat dieser Berechnung, racket_frame, ist eine zusammengesetzte Abbildung. Wertet
man diese – zum Beispiel in der Operatormethode “Sampling” – aus, so werden Schritt für Schritt
sämtliche elementaren Operationen durchgeführt, die bei Ausführung von m_wire zusammengesetzt werden.
4.3. ZUSAMMENFASSUNG
63
Genau diese zusammengesetzte Abbildung kann außerdem in der C++-Implementation, dem
Genmod-Paket, zur Laufzeit angelegt werden, also das Resultat der Ausführung der Funktion –
des Metashapes – m_wire.
GENMOD-Programme sind letztlich aber wie funktionale Programme aufgebaut, in sofern,
als keine destruktiven Updates von Objekten benutzt werden. In keinem Beispiel findet sich ein
Ausdruck wie MAN a=b+c; a=a+d;, auf GENMOD-Objekte werden nach der Konstruktion nur
noch Operatormethoden wie Auswertung oder Bestimmung der Ableitung angewandt. Das Verbot destruktiver Updates in funktionalen Sprachen ist also nur konsequent. – Gleichzeitig ist die
Haskell-Implementation zunächst weniger aufwendig, weil Funktionen bequem als Parameter benutzt werden können, die Operatormethode “Auswertung” ist Sprachbestandteil. Damit läßt sich
das GENMOD-Beispiel eins zu eins nach Haskell übertragen:
m_wire_fct cross wire u v = q where
that
= derivOne wire u
t
= normalize that
n
= V3 (-(y t)) (x t) 0.0
wire_u = evalCurve wire u
crss_v = evalCurve cross v
p
= add (mult (x crss_v) n) wire_u
q
= V3 (x p) (y p) (y crss_v)
m_wire cross wire = SrfFct (\u v->m_wire_fct cross wire u v)
racket_frame = m_wire cross_crv wire_crv
Gleichzeitig muß bemerkt werden, daß die anderen Operatormethoden aber nicht Sprachbestandteil von Haskell sind. Für eine tatsächliche genaue Übertragung müßte eine Funktion derivOne angewandt auf eine Kurve wieder eine Kurve zurückgeben; tatsächlich ist das für eine Klasse
SrfFct nicht effizient zu implementieren, insbesondere was die für die implizite Modellierung so
wichtige Inklusionsfunktion angeht, denn Operatormethoden sind lokal rekursiv, und die Effizienz der Implementierung von GENMOD beruht darauf, daß ein MAN-Objekt weiß, wie es die
rekursiven Aufrufe der Operatormethode zu verknüpfen hat. Auswertung ist aber zunächst für die
parametrische Modellierung die wichtigste Operatormethode, daher kann man in obigem Beispiel
in aller Kürze SrfFct eine anonyme Lambda-Funktion zuweisen. - Noch knapper läßt sich das als
Instanz der Flächenklasse formulieren wie in Beispiel 6.2.7. Die Haskell-Implementation bewegt
sich also auf der gleichen Ebene wie eine Ableitung von t_SurfaceParam des cns-Paketes, deren
evaluate()-Aufruf mit obiger Funktion überschrieben wurde, die auf C++-Ebene zum GenmodPaket hinzugelinkt wird, bedeutet also zunächst keinen Vorteil, solange kein Haskell-Interpreter
verwendet wird.
Zum anderen ist zu bemerken, daß das Modellieren mit Abbildungen eine abstraktere Modellierung als in interaktiven Modellern ermöglicht, aber auch erfordert. Jede Abbildung erzeugt ein
neues Objekt, dies ist in GENMOD wie im Genmod-Paket wie in Haskell-Genmod der Fall, ebenso wie in interaktiven Modellern, die ein unbegrenztes Undo ermöglichen. Konsequenz dieser Tatsache ist, daß Modelle möglichst lange in nicht-approximierter Form vorliegen sollten. Um dies
zu verdeutlichen, soll eine einfache Translation einer Kugel als Beispiel herangezogen werden.
Generativ bedeutet das, auf ein Kugelobjekt ein Transformationsobjekt anzuwenden, alles, was
passiert, ist die Komposition zweier Abbildungen zu einer neuen zusammengesetzten Abbildung.
Um die bewegte Kugel zu verwenden, wendet man eine Operatormethode an; beim Sampling beispielsweise werden stets zwei Abbildungen ausgewertet, erst ein Punkt auf der Kugel berechnet,
dann dieser bewegt.
64
KAPITEL 4. GENERATIVE MODELLIERUNG
Stellt man sich das gleiche für die Kugel in approximierter Form vor, so kann man zum einen
eine – interaktive oder skriptgesteuerte – Abbildung anwenden, die einzelne Ecken eines Polyeders
bewegt, oder zum anderen eine, die ein Polyeder im Ganzen bewegt. – Die erste Lösung wäre
aufwandsmäßig eine Katastrophe, die zweite Methode würde zumindest ein zweites, verschobenes
Objekt erzeugen. Eine dritte Möglichkeit, die Veränderung der ersten Kugelapproximation durch
Verschieben von Knoten, existiert zunächst einmal wegen des Verbotes destruktiver Updates nicht,
die unverschobene Kugel könnte nicht anderweitig referenziert werden.
Konsequenz dieser Überlegung ist also ein völliges Abgehen vom approximativen Modellieren
wie in der ersten Modellergeneration. Der Schritt vom parametrischen zum Generativen Modellieren bedeutet also aus Effizienzgründen, daß möglichst lange mit der unausgewerteten Form von
Modellen alleine als Komposition von Werkzeugen gearbeitet wird. Gleichzeitig ist dies nicht eine Not, sondern eine Tugend, weil es so endlich möglich ist, jeden Punkt einer Oberfläche mit
Maschinengenauigkeit zu berechnen, es geht also an keinem Punkt des Modellierprozesses Information über die Konstruktion verloren.
Kapitel 5
Implementationen von Genmod in C++
und Haskell
Wie im vorigen Kapitel 4 über die Grundideen einer Realisierung der Generativen Modellierung
in C++ und Haskell beschrieben, ähneln sich Grundkonzepte in beiden Paketen stark. Dazu gehört
einerseits das Konzept der Kurven und Flächen als parametrische Abbildungen, andererseits die
Idee, eine Menge von Basisklassen als Kern für eine erweiterbare objektorientierte Klassenhierarchie zugrundezulegen. Im Genmod-Paket des MRT geht es dabei aber nicht nur um die Klassen,
mit denen modelliert wird, sondern auch um die Implementation eines Abbildungskonzeptes, das
C++ als imperative Sprache so nicht kennt. Die C++-Implementation ist dabei recht umfangreich,
es entfallen inklusive der benötigten Hilfsklassen dabei auf das Genmod-Paket 84 Klassen, auf
die Bibliothek der Kurven- und Flächen, das cns-Paket, noch weitere 68. In diesem Kapitel sollen daher lediglich die wesentlichen Konzepte und der Umfang der implementierten Verfahren
vorgestellt werden.
Neben dem Generativen Kern ist bei der C++-Implementierung insbesondere Interesse, in welcher Weise Kurven und Flächen des cns-Paketes realisiert sind. C++- und Haskell-Implementation
benutzen dabei die gleiche Grundidee, daß “abstrakte” Basisklassen t_ParametricCurve und t_ParamSurface in C++ und Curve und Surface in Haskell bereits die notwendigen differentialgeometrischen Größen berechnen können, wenn in einer abgeleiteten Klasse bzw. einer Instanz die
virtuelle evaluate() bzw. evalCurve oder evalSurf-Funktionen überschrieben werden.
Im folgenden werden die differentialgeometrischen Grundbegriffe für Kurven und Flächen nur
in soweit eingeführt, wie sie für die Implementation von Relevanz sind. Der interessierte Leser sei
auf [do 93] und auf die Kapitel 11 und 22 in [Far90] verwiesen.
Zur Berechnung von Tangenten etc. bedient man sich der näherungsweisen Ableitung über
Differenzenquotienten. Nach der Taylorschen Formel gilt für eine Funktion f , die in der Umgebung von t unendlich oft differenzierbar ist:
1
1
1
f (t + h) = f (t ) + h f 0 (t ) + h2 f 00 (t ) + h3 f (3) (t ) + h4 f (4)(t ) + :::
2
6
4!
Die erste bzw. die zweite Ableitung erhält man dann als Grenzwert des ersten und zweiten
Differenzenquotienten für h ,! 0.
∆ f (t ) =
f (t + h) , f (t , h)
;
2h
∆2 f (t ) =
f (t + h) + f (t , h) , 2 f (t )
h2
Die Berechnung des Frenet’schen Dreibeins von Kurven und Flächen läßt sich dann mit Differenzenquotienten auf die Auswertungsfunktion zurückführen.
65
66
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
Die erste Ableitung einer Raumkurve entspricht ihrer Tangente, der Differenzenquotient zeigt
in Richtung zweier benachbarter Kurvenpunkte. Die zweite Ableitung steht im Grenzwert senkrecht dazu und zeigt in Richtung auf den Mittelpunkt des Krümmungskreises, das ist der Kreis,
der die Kurven in einem bestimmten Punkt in zweiter Näherung berührt. Die euklidische Länge
der zweiten Ableitung ist die Krümmung, für Kurven, die nach der Bogenlänge parametrisiert
sind, ist das κ = j f 00 (t )j, ihre Inverse ρ = κ1 ist im Falle nichtverschwindender Krümmung gerade
der Radius des Krümmungskreises.Für Kurven, die nicht nach der Bogenlänge parametrisiert sind,
j f (t ) f (t )j
erhält man die Krümmung durch κ(t ) = j f (t )j3 , ist dabei das Kreuzprodukt.
0
00
0
t
t
m
m
Die ersten beiden Ableitungen einer Kurve mit nichtverschwindender Krümmung sind linear unabhängig, durch Orthonormalisierung nach Gram-Schmidt erhält man die Basisvektoren des
Frenet’schen Dreibeins in einem Kurvenpunkt. Das sind die Tangente t, der Hauptnormalvektor oder die Normale m, und der Binormalvektor b von f in t:
t=
f 0 (t )
j f 0(t )j ;
m = bt;
b=
f 0 (t ) f 00 (t )
j f 0(t ) f 00(t )j
Dabei ist zu beachten, daß in einem Punkt, wo eine zweidimensionale Kurve ihre Krümmungsrichtung ändert, die Krümmung null ist und der normalisierte Normalvektor schlagartig die Seite
der Kurve wechselt. In solchen singulären Punkten der Ordnung 1 ist die Normale als Funktion von
t also unstetig. Tangente und Normale spannen die Schmiegeebene der Kurve auf, bei planaren
Kurven ist das gerade die Ebene.
In Flächenpunkten kann man ebenfalls ein lokales Koordinatensystem definieren, allerdings
im allgemeinen kein Orthonormalsystem. Und zwar bedient man sich hier der partiellen Ableitungen der Fläche in die Parameterrichtungen zusammen mit dem Normalvektor der Fläche.
tu(u; v) =
∂s(u; v)
;
∂u
tv (u; v) =
∂s(u; v)
;
∂v
n(u; v) = tu tv
In diese Definitionen werden in der Implementation in den Basisklassen die ersten bzw. zweiten Differenzenquotienten eingesetzt, um das Frenet’sche Dreibein zu berechnen.
Für Kurven und Flächen erhält man damit insgesamt die gezeigten lokalen Richtungsvektoren. Koordinatensysteme sind hier stets rot, grün, blau für x-, y-, z-Achse, bzw. für Tangente,
Hauptnormale, Binormale und für tu , tv , n dargestellt. Der Flächenpunkt in der Abbildung hat die
Koordinaten (0:8; 0:1).
5.1. IMPLEMENTATION IN C++
67
5.1 Implementation in C++
In diesem Kapitel werden die C++-Klassen im einzelnen beschrieben, die den Kern der GenmodImplementation bilden. Des weiteren wird die Kurven- und Flächenbibliothek vorgestellt und
schließlich ihre Integration in Genmod mit Hilfe von Wrapper-Klassen vorgeführt.
Es gilt dabei die MRT-Namenskonvention, abgeleitete Klassen verlängern die Namen ihrer
Basisklassen. Das hat den Grund, daß OOP-gemäß abgeleitete Klassen Spezialisierungen ihrer Basisklassen darstellen. Dies gilt gleichermaßen für Namen von Template-Instantiierungen, so wird
aus einer Basisklasse t_GmVar und einer abgeleiteten Templateklasse t_GmVarTemplate durch
Instantiierung eine Klasse t_GmVarReal.
5.1.1
Der Genmod-Kern
t_GmRegister
t_GmAdmin
t_GmAdminMap
t_GmAdminType
t_GmVarPtr
t_GmMAP
t_GmVar
t_GmMapRebind
t_GmMapCompose
_DagOrder
t_GmSignature
t_GmMapConstTemplate<C>
t_GmMapVarTemplate<C>
t_GmVarTemplate<C>
t_GmVarPtrTemplate<C,V,P>
Dies sind die Kernklassen von Genmod. Sie zerfallen in verschiedene Kategorien, einmal Verwaltungsklassen, dann Klassen für Variablen und Klassen für Abbildungen. Die zentrale Klasse ist
t_GmMAP, deren wichtigste Funktionen kurz vorgestellt werden sollen.
class t_GmMAP : public t_GmVar
{
public:
t_GmSignature& getBoundSignature();
// set return value, get input-signature to append input vals to
t_GmMAP& operator() (t_GmMAP& in);
// bind and check if everything is ready for execution
t_Bool checkBinding ();
// do what you are made for
t_Bool execute ();
// get return value, may be NULL if function has no return value
const t_GmVarPtr& result ();
// get rid of all input vars and maps
void unbind () ;
protected:
virtual t_Bool bind ();
virtual t_Bool evaluate () ;
// =0;
virtual t_Bool fillSignature () ; // =0;
t_GmSignature& signature(const t_GmVarPtr& p);
t_GmMapList evalMaps;
t_GmSignature localVars;
t_Bool bindingIsDone;
};
Diese Klasse ist dazu entworfen, daß ein Anwendungsprogrammierer leicht eigene Abbildungen durch Ableitung erzeugen kann. Daher ist es wichtig, zu verstehen, wie das Zusammenspiel
von t_GmMAP und einer abgeleiteten Klasse funktioniert. Als Beispiel soll die in 4.1.1 gezeigte
Ableitung t_GmMapPt3DCross dienen.
68
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
Abbildungsobjekte werden in der Regel mit Hilfe des Standardkonstruktors erzeugt. Als nächstes werden mit dem ()-Operator die Eingabeabbildungen gesetzt. Dabei wird erst sichergestellt,
daß diese gebunden sind, weil sie dann erst Rückgabevariablen angelegt haben, Zeiger auf diese
Abbildungen an die dynamische Liste der Eingabeabbildungen angefügt.
Der nächste Schritt, das Binden, erfolgt, wenn es explizit durch checkBinding() angefordert
wird, oder wenn ein execute()-Aufruf geschieht. Das Binden selber erfolgt ein einziges Mal in der
zentralen Funktion bind(), die nur in Ausnahmefällen in abgeleiteten Klassen überschrieben wird,
etwa bei t_GmMapCompose. In bind() wird die virtuelle Funktion fillSignature() aufgerufen, die,
wie in 4.1.1 für t_GmMapPt3DCross vorgeführt, die Verbindung zwischen den privaten Variablen
der abgeleiteten Klasse und dem Verwaltungsmechanismus der Basisklasse herstellt. Innerhalb
von bind() wird nun ein Typabgleich zwischen der Liste der über operator() eingesammelten Eingabeabbildungen und der in fillSignature gesetzten Signatur durchgeführt. Dabei wird einerseits eine Rückgabevariable vom spezifizierten Typ gesetzt, der immer als erstes mit signature(Rückgabe)
angegeben wird, andererseits werden die übergebenen lokalen Zeiger der abgeleiteten Klasse auf
sinnvolle Werte gesetzt, nämlich auf die Inhalte der Rückgabevariablen der Eingabeabbildungen.
Ist dies erfolgt, so wird bindingIsDone auf Wahr gesetzt, und fortan kann die Abbildung per
execute() ausgeführt werden. Über result() kann der Wert der Berechnung dann ausgelesen werden, wenn das zurückgegebene Referenzpointer-Objekt dereferenziert wird. – Dadurch, daß die
Verwaltungsarbeit automatisch beim Anfügen von Eingaben oder beim Ausführen selber erledigt
wird, ist nicht nur das Schreiben von Ableitungen recht einfach, sondern auch im Rahmen des
Möglichen die Benutzung:
t_Real a=3,b=4;
t_GmMapRealVar a0(&a);
t_GmMapRealVar b0(&b);
t_GmMapRealAdd p;
p(a0)(b0);
p.execute();
cout << "3+4=" << p.result() << endl;
Bei der Einführung einer neuen Variablenklasse werden dann sechs Templates instantiiert,
das Variablentemplate selber und zwei konstante Abbildungen zur Kommunikation mit C++Variablen, sowie drei Zeiger-Templates für die drei ersten Klassen mit Hilfe der von Martin Fischer in [Fis95a] vorgestellten Reference-Pointer. Das ermöglicht der Klasse t_GmSignature, die
eigentlich nur eine Liste von Variablen hält, sich nicht um das Löschen angelegter Variablen oder
Abbildungen kümmern zu müssen; solange diese noch referenziert werden, werden sie nicht gelöscht.
Schließlich ist die Grundidee der Generativen Modellierung, daß sich ein Anwender eine Bibliothek eigener Werkzeuge definieren kann. Um dies zu unterstützen, können sich Abbildungen
einen Namen geben, der an zentraler Stelle von t_GmRegister verwaltet wird. Man könnte sich
vorstellen, daß ein interaktiver Modeller die Liste dieser Namen anbietet, aus denen sich ein Modellierer eine passende Abbildung sucht. Hat er eine eigene Abbildung zusammengestellt, wird
eine Kopie dieser Verwaltungsinstanz zurückgegeben und gehört ebenfalls zum Repertoire, das
man bei Bedarf benutzen kann. Dort werden neben Abbildungen aber auch Typen gehalten, beide
Administrator-Klassen sind abgeleitet von der Basisklasse t_GmAdmin.
5.1. IMPLEMENTATION IN C++
5.1.2
69
Die Kurven- und Flächenbibliothek
Das cns-Paket ist unabhängig von Genmod entstanden, um eine Hierarchie von Kurven- und
Flächenklassen zur allgemeinen Verwendung im MRT zu haben. Beispielsweise sind die parametrischen biquadratische Bézierflächen dazu verwendet worden, um ein MRT-Objekt zu definieren, das in msd-Dateien verwendet werden kann. Dazu wurde die allgemeine parametrische
Fläche des cns-Paketes um einen Algorithmus zur Berechnung von Strahlschnitten ergänzt, das
Bézierclipping-Verfahren [CS96], um die Strahlschnittanforderung von t_Object zu beantworten.
Obwohl beide Pakete, Genmod und cns, aufeinander aufbauen, ist Genmod zur Übersetzung
von cns nicht notwendig. Einerseits ist eine Trennung im Sinne einer Modularisierung, andererseits mußte im Rahmen dieser Arbeit eine Hierarchie gefunden werden, um eine wohldefinierte
Basis zur Integration der vielen Repräsentationen von Kurven, Flächen und allgemeinen Transformationen der Computergrafik zu besitzen. Die vorhandenen Klassen sind daher mehr als repräsentativer Querschnitt denn als erschöpfende Implementation zu verstehen.
Zudem ist analog zur Klasse t_3DVector des MRT-Paketes innerhalb des cns-Paketes eine
Klasse t_PtRb entstanden, die auf balancierten Rot-Schwarz-Bäumen [CLR90] beruht. Damit ist
es möglich, n-dimensionale Vektoren zu implementieren, die auch für große n günstiges Laufzeitverhalten zeigen, denn Zugriff auf eine Komponente kostet Zeit O(logk), k Anzahl der nichtverschwindenden Komponenten. n-dimensionale Vektoren sind so definiert, daß der Eintrag ungleich
null mit dem größten Index die Dimension n des Vektors angibt, wobei ein Vektor niedrigerer
Dimension als Teil des kanonischen Unterraums des Rn für n beliebig betrachtet wird. Praktisch
heißt das, daß alle Komponenten xi mit i > n gleich null sind. Für diesen Vektortyp sind alle Operationen wie Addition, skalare Multiplikation etc. mit Ausnahme des dreidimensionalen Kreuzproduktes wohldefiniert. Dieses wurde nur für die ersten drei Komponenten zweier n-dimensionaler
Vektoren definiert, damit eine Template-Kompatibilität zwischen t_PtRb und t_3DVector hergestellt werden konnte. Dadurch lassen sich Vektoren beiden Typs als Parameter in Templateklassen
einsetzen, und je nach Anwendung kann man so einmal recht effizient in 3D rechnen, andererseits
besitzt man aber auch die Möglichkeit, Kurven und Flächen im n-Dimensionalen zu betrachten.
Differentialgeometrisch machen dort allerdings dreidimensionale lokale Koordinatensysteme wenig Sinn, immerhin funktioniert die Differentiation jedoch wie in der Einleitung beschrieben nach
wie vor in n Dimensionen. Dazu sind die Kurven und Flächen zu Templateklassen geworden. Im
folgenden wird dabei der für die 3D-Modellierung relevante Fall m; n = 3 betrachtet, also für Pt in
diesem Fall t_3DVector eingesetzt. Ähnlich wie in der Haskell-Implementation hat man somit Kurven, Flächen und Funktionen als Abbildungen R ! R3, R2 ! R3 und R3 ! R3. Die Basisklassen
von Kurven und Flächen benutzen die virtuelle Auswertungsfunktion evaluate() (siehe 4.1.1.1),
um Ableitungen in numerischer Näherung zu berechnen. Die Funktionen zur Berechnung der ersten und zweiten Ableitung sind aber ebenfalls virtuell, um effizientere Ableitungsberechnungen
in abgeleiteten Klassen zuzulassen.
Insgesamt besitzt die Kurvenklasse die folgenden Memberfunktionen:
evaluate
deriv
deriv2nd
tangent
mainNormal
biNormal
curvature
length
domain
Die Funktion domain liefert ein Objekt vom Typ t_Interval, das ist der Parameterraum der
Kurve. Die Krümmung wird nach der Formel in der Einleitung aus erster und zweiter Ableitung
berechnet, die Länge einer Kurve c wird berechnet, indem über die Länge der Tangente integriert
wird, falls l (t0) = t0, ist c nach der Bogenlänge parametrisiert:
l (t0) =
Z t0
0
jc0(t )j dt
70
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
Die Flächenklasse enthält folgende Memberfunktionen.
evaluate
deriv
deriv2nd
gradient
isTriangle
normal
tangent
dirTangent
twist
area
domain
normCurvat
gaussCurvat
meanCurvat
Die Ableitungsfunktion bekommt einen Boole’schen Wert, der sagt, ob in u oder in v-Richtung
∂s (u v) ∂s (u v)
abgeleitet werden soll, der Gradient ist eine Zeile der Ableitungsmatrix ( i∂u ; i∂v ), dirTangent berechnet die Richtungsableitung, und der Twist ist die gemischte zweite Ableitung suv einer
Fläche s. Die Fläche über einem Intervall I J ist:
;
Area(I J ) =
Z Z
I J
q
;
susu sv sv , (susv )2 du dv
Gauß’sche, mittlere und Normalkrümmung können mit Hilfe der zweiten Ableitungen einer
Fläche berechnet werden. — Zusätzlich besitzen Flächen eine virtuelle Funktion, die angibt, ob ihr
Parameterbereich drei- oder viereckig ist. Das zweidimensionale Standardintervall ist [0; 1] [0; 1],
bei dreieckigen Flächen ist zusätzlich u + v 1.
Die folgende Übersicht gibt einen Eindruck von der Hierarchie der implementierten Kurven
und Flächen.
t_CurveParametric<Pt>
t_Function<Pt>
t_SurfaceParam<Pt>
t_CurveLine<Pt>
t_FunctRotQuat<Pt>
t_SurfaceBicubicBezier<Pt>
t_FunctionTurbulence<Pt>
t_SurfaceBezierTri<Pt>
t_FunctTensor<Pt>
t_SurfaceCoons<Pt>
t_CurveTensor<Pt>
t_CurveBSpline<Pt>
t_CurveBezier<Pt>
t_SurfaceCoonsTri<Pt>
t_SurfaceSphere<Pt>
t_SurfaceTensor<Pt>
Eine besondere Rolle in der Geometrischen Modellierung spielen Tensorprodukt-Kurven und
-Flächen. Bézierkurven und B-Splines beispielsweise fallen in die Kategorie der Tensorkurven.
Diese zeichnen sich dadurch aus, daß man skalare Basisfunktionen A0(t ); :::; An(t ) über einem
reellen Intervall I und Kontrollpunkte p0 ; ::: pn 2 R3 hat, sodaß sich die Kurve in parametrischer
Form als ein Skalarprodukt eines reellen mit einem Punktvektor darstellen läßt: c(t ) = ∑ni=0 Ai (t ) pi
Damit sich der Verlauf der Kurve intuitiv steuern läßt, verlangt man, daß Kurvenpunkte affine Konvexkombinationen der Kontrollpunkte sind. Das bedeutet, die Basisfunktionen sind stets positiv
und eine Partition der Eins: Für alle t 2 I gilt, daß Ai (t ) 0 für i = 1; :::; n ist und ∑ni=0 Ai (t ) = 1.
Verallgemeinert man diese univariate auf bi- oder trivariate Darstellungen, so erhält man das Tensorprodukt als verallgemeinertes Skalarprodukt. Das bedeutet, man hat im bivariaten Fall ein zweidimensionales und im trivariaten Fall ein dreidimensionales regelmäßiges Gitter von Kontrollpunkten. Man kann Tensorproduktflächen folgendermaßen mit einer Matrix von Kontrollpunkten
5.1. IMPLEMENTATION IN C++
71
darstellen:
n
m
s(u; v) = ∑ ∑ Ai (u) B j (v) pi j = (B0(v); :::;
i=0 j=0
0
B
Bm(v)) @
p00
..
.
pm0
..
.
p0n
..
.
1 0 A (u) 1
0
C
AB
@ ... C
A
pmn
An(u)
Gemischte Tensorproduktflächen können dabei als Ai die Bernsteinpolynome der Bézierkurven und als Bi die B-Spline-Basisfunktionen benutzen. – Ein trikubisches Tensorproduktvolumen
mit Basisfunktionen Ai (x); B j (y); Ck(z) dagegen sieht in Summenschreibweise so aus:
l
m
v(x; y; z) = ∑ ∑
n
∑ Ai(x) B j (y) Ck (z) pi jk
i=0 j=0 k =0
Die Auswertung einer bikubischen Funktion kann man so interpretieren, daß zunächst vier
Kurven in u ausgewertet werden, diese vier Punkte nimmt man als Kontrollpunkte für die kubische
v-Kurve. Entsprechend kann man eine trikubische Funktion so interpretieren, daß zunächst die
Kontrollpunkte einer bivariaten Fläche berechnet werden. Die Auswertung kann so visualisiert
werden.
Die Auswertung einer k-variaten Tensorproduktfunktion kann man folglich auf die Auswertung einer (k , 1)-variaten Funktion zurückführen. Genau diese Rekursion wird nun bei t_FunctTensor benutzt, es gibt einen Konstruktor, der ebenfalls eine Tensorfunktion erhält, zusammen mit
einer Tensorproduktkurve, die für die Auswertung der Kontrollpunkte benutzt wird, und einer Information über den Rekursionsindex, damit die zur Interpolation zu benutzende Koordinate aus
dem Parametervektor herausprojiziert werden kann.
class t_FunctTensor : public t_Function<Pt>
{
public:
t_FunctTensor (t_CurveTensor<Pt>& evalCrv,
t_FunctTensor<Pt>& changeTensor, t_PtrList* contrPt);
virtual Pt& evaluate (const Pt& t) ;
virtual t_Bool setControlPoints (t_PtrList* contrPt);
protected:
t_ListDl<Pt*>* cp;
int parameterIndex;
t_CurveTensor<Pt>* curve;
t_FunctTensor<Pt>* tensor;
t_PtrList* cPt;
};
Auf diese Weise genügt es, eine virtuelle Basisklasse für Tensorprodukte zu definieren, um
die Verwaltung des Kontrollpunktvektors an zentraler Stelle zu erledigen. Eine Tensorfunktion hat
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
72
im univariaten Fall Zugriff auf eine Liste von Punkten, im bivariaten auf eine Liste von Liste von
Punkten und so weiter. Eine Tensorproduktfläche ist daher eine Spezialisierung einer allgemeineren Funktion und erhält im Konstruktor zwei Tensorproduktkurven, die im wesentlichen nur die
zu benutzenden Basisfunktionen repräsentieren. Neben den beiden implementierten Kurven gibt
es noch eine ganze Reihe weiterer Verallgemeinerungen von Splines, wie Beta-Splines [BBB87],
ν-Splines und τ-Splines [HL92].
Eine B-Spline-Kurve ist nicht nur durch den Kontrollvektor gegeben, sondern es gehört auch
eine Aufteilung des der Kurve zugrundeliegenden Parameterinterintervalles I = [t0; tn+k] zur Definition, der Knotenvektor T = (t0; :::; tn+k) (nicht streng) aufsteigender reeller Zahlen. Dabei ist
k der im allgemeinen niedrige Grad (bei kubischen Kurven 4), n die Anzahl der Punkte des Kontrollpolygons. Die Definition der Basisfunktionen geschieht über die bekannte Boehm-Rekursion:
Nik (t ) =
t , ti
ti+k,1 , ti
Nik,1(t ) +
ti+k , t k,1
N (t ) ; Ni1 (t ) = χ[ti ti+1 ] ; i = 0; :::; n + k , 1
ti+k , ti+1 i+1
;
Eine Basisfunktion Nik hat dabei als Träger das Teilintervall [ti; ti+k ], daher haben für ein
t 2 [ti; ti+1] nur die Kontrollpunkte pi,k+1; :::; pi Einfluß auf die Form der Kurve (Lokalitätseigenschaft von B-Splines). Diese Kontrollpunkte werden dann im de-Boor-Algorithmus benutzt,
um die affine Kombination dieser Punkte zu bilden. Der Aufwand dieses Algorithmus’ ist O(k2),
also quadratisch im i. a. niedrigen Grad. Ein Problem besteht bei der Verwendung dynamischer
Datenstrukturen wie verketteter Listen daher im schnellen Finden des entsprechenden Index. In
der vorliegenden Implementierung ist dies dadurch gelöst, daß die gleichen Rot-Schwarz-Bäume
für die Knotenvektoren benutzt werden, wie sie bei den n-dimensionalen Punkten verwandt werden. Diese gestatten das Finden des richtigen Intervalls in logarithmischer Zeit. Zudem enthalten
sie im gefundenen Intervall nicht den Parameterindex, sondern einen Listeniterator [SL95], der in
die doppelt verkettete Liste der Kontrollpunkte zeigt, sodaß der de-Boor-Algorithmus gleich an
der richtigen Stelle pi,k+1 aufsetzt. Damit erhält man insgesamt die sehr guten Auswertungszeiten, die B-Splines mit vielen Kontrollpunkten schneller, beherrschbarer und im allgemeinen auch
genauer als Bézierkurven machen.
Die Verwendung eines dynamischen Knotenvektors ist wichtig, um ein schnelles Einfügen
von Kontrollpunkten etwa mit Hilfe des Oslo-Algorithmus [Far90] zu ermöglichen, wobei auch
im Knotenvektor lokale Einfügungen entstehen.
Zusätzlich zur beschriebenen allgemeinen Implementation von Tensorproduktflächen mit Tensorkurven beliebigen Grades ist ebenfalls eine fest bikubische Version der Bézierflächen implementiert, die einen Vektor von sechzehn Kontrollpunkten erhält und deren Auswertungsfunktion
stark optimiert ist, indem sämtliche Schleifen “ausgerollt” wurden. Diese Klasse enthält zusätzlich
Funktionen, um die Fläche an einem u bzw. an einem v-Wert in zwei oder vier Einzelflächen aufzuteilen und die entstehenden Kontrollpunkte zurückzugeben. Diese Funktionen wurden ebenfalls
optimiert, da sie die Grundoperation im erwähnten Bézierclipping-Algorithmus zum Strahlschnitt
darstellen.
Die Basisfunktionen n-ten Grades Bnij der Bézierkurven, die Bernsteinpolynome, haben eine
Verallgemeinerung auf baryzentrische Dreieckskoordinaten. Neben v = 1 , u und i + j = n kann
man nämlich auch w = 1 , u , v und i + j + k = n zerlegen:
Bnij (u; v) =
n! i j
uv ;
i! j!
Bnijk (u; v; w) =
n!
ui v j wk
i! j!k!
Betrachtet man positive (u; v) mit u + v 1, so ist w 0 und man kann eine Dreiecksfläche
parametrisieren, [HL92], Kapitel 6.3.2, mit Kontrollpunkten pi jk mit i + j + k = n. Neben diesen
Bézierdreiecken ist der andere implementierte dreieckige Flächentyp das Dreiecks-Coons-Patch.
5.1. IMPLEMENTATION IN C++
73
Coons-Patches existieren ebenfalls in der viereckigen Form und zeichnen sich dadurch aus, daß
diese Flächen alleine durch die Angabe von Randkurven schon definiert werden, [Far90], Kap.
21. Es wird dabei ein bi- bzw. trilineares Blending durchgeführt, das sich durch sogenannte Boole’sche Summen ausdrücken läßt, wenn man die Randkurven einer Fläche p(u; v) in der Form
p(u; 0); p(u; 1); p(0; v) und p(1; v) bzw. p(0; t ; 1 , t ) etc. ausdrückt. Die Randkurven müssen allerdings die naheliegende Forderung erfüllen, daß sie sich in den Ecken treffen, damit man von
Eckpunkten wie p(0; 0) sprechen kann. Die Abbildungen in der Einführung zu diesem Kapitel auf
Seite 66 stellen beispielsweise ein viereckiges Coonspatch dar.
Zu guter Letzt existieren in cns noch eine Kugel, parametrisiert in Polarkoordinaten, eine
Strecke zwischen zwei Punkten und eine Turbulenzfunktion nach der üblichen Formel [WW92],
7.2.2:
k
turbulence(u; v) = ∑ abs
i=0
1
2
noise(2iu; 2i v)
i
:
Die periodische noise-Funktion ist in diesem Fall über eine Tensorproduktfläche als zufälliges
Höhenfeld einstellbaren Grades implementiert.
5.1.3
Integration von Kurven und Flächen in Genmod
Um mit Kurven und Flächen generativ modellieren zu können, werden nun der Genmod-Kern
einerseits und die Kurven und Flächen andererseits mit Hilfe der vorgestellten Templateklassen
kombiniert. Zunächst werden dazu die Variablenklassen definiert, dann die wesentlichen Memberfunktionen in Abbildungen verpackt. Aus diesen lassen sich Anwendungen gewinnen, die im
abschließenden Bericht über Erfahrungen mit Genmod in Kapitel 6 besprochen werden.
5.1.3.1
Variableninstanzen
long
t_Real
t_3DVector
t_PtRb
t_BRep
t_CurveParametric3D
t_CurveBezier3D
t_CurveMap3D
t_CurveBSpline3D
t_CurveLine3D
t_SurfaceParam3D
t_SurfaceTensor3D
t_SurfaceMap3D
t_SurfaceSphere3D
t_SurfaceCoons3D
Für diese Klassen werden sechs Template-Instanzen erzeugt, die durch typedef nach folgendem
Muster umbenannt werden:
typedef t_GmVarTemplate<t_Real> t_GmVarReal;
typedef t_GmMapVarTemplate<t_Real> t_GmMapRealVar;
typedef t_GmMapConstTemplate<t_Real> t_GmMapRealConst;
Insbesondere ist hier von Bedeutung, daß die Ableitungshierarchie zwischen Variablenklassen
ebenfalls spezifiziert werden kann. Mit Hilfe der Runtime-Type-Information [Fis95b] kann die
Typüberprüfung beim Binden von Abbildungen in der Weise erfolgen, daß eine Abbildung, die
beispielsweise eine parametrische Kurve auswertet, eine Variable vom syntaktisch unterschiedlichen Typ t_CurveBezier3D erhalten kann.
Variablenklassen müssen den Anforderungen genügen, sich per <<-Operator auf einen Stream
ausgeben zu können, sie müssen über einen Standard- und einen Copykonstruktor verfügen und
Zuweisung per =-Operator zulassen.
Die im vorigen Abschnitt vorgestellten Kurven und Flächen sind hier in ihrer dreidimensionalen Version enthalten. Daneben gibt es zwei weitere Typen, um Abbildungen der entsprechenden
74
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
Signaturen in eine Kurve oder Fläche zu verpacken, t_CurveMap ist von t_CurveParametric und
t_SurfaceMap ist von t_SurfaceParam abgeleitet, die jeweiligen Konstruktoren erhalten Objekte
vom Typ t_GmMAP. Ableitungen werden für diese Klassen näherungsweise bestimmt.
Für das einfache Rechnen mit Fließkommazahlen wurden die üblichen arithmetischen Verknüpfungen und die Funktionen der Mathematikbibliothek in Abbildungen übersetzt.
t_GmMapRealAdd
t_GmMapRealAssign
t_GmMapRealCeil
t_GmMapRealCos
t_GmMapRealDiv
t_GmMapRealExp
t_GmMapRealFabs
t_GmMapRealFloor
t_GmMapRealInverse
t_GmMapRealLog
t_GmMapRealMinus
t_GmMapRealMult
t_GmMapRealNegate
t_GmMapRealSin
t_GmMapRealTan
Zum Umgang mit dreidimensionale und n-dimensionalen Vektoren existieren neben Addition,
Subtraktion, Skalarprodukt usw. auch Funktionen, um reelle Zahlen zu Vektoren zusammenzusetzen und sie aus Vektoren herauszuprojizieren. Erwähnenswert wäre noch, daß auch die Projektion
als Operation implementiert ist, die zu v, w die Projektion von w auf v berechnet und ein Vielfaches
von v zurückgibt:
proj(v; w) =
v; w >
v;
< v; v >
<
t_GmMapPt3DAdd
t_GmMapPt3DConcatReal
t_GmMapPt3DCoord
t_GmMapPt3DCross
t_GmMapPt3DDot
t_GmMapPtRbAdd
t_GmMapPtRbConcatPtRb
t_GmMapPtRbConcatReal
t_GmMapPtRbCoord
t_GmMapPtRbCross
wegen
v; w >
v,w v = 0
< v; v >
<
t_GmMapPt3DLength
t_GmMapPt3DMinus
t_GmMapPt3DNegate
t_GmMapPt3DNormalize
t_GmMapPt3DProject
t_GmMapPtRbDot
t_GmMapPtRbLength
t_GmMapPtRbMinus
t_GmMapPtRbNegate
t_GmMapPtRbNormalize
t_GmMapPt3DScalDiv
t_GmMapPt3DScalMult
t_GmMapPt3DSet
t_GmMapPt3DSpat
t_GmMapPtRbProject
t_GmMapPtRbScalDiv
t_GmMapPtRbScalMult
t_GmMapPtRbSet
t_GmMapPtRbSpat
Die wesentlichen Memberfunktionen von Kurven und Flächen bilden reelle Parameter auf
Vektoren ab, insbesondere existieren Abbildungen, die statt eine Abbildung auszuwerten diese
Abbildung in ein t_CurveMap bzw. ein SurfaceMap-Objekt verwandeln und dieses als Rückgabewert liefern. Um aus Randkurven eine Fläche zu bilden, kann man Coons-Flächen verwenden,
die Sitzfläche des Stuhles aus Beispiel 6.1.7 ist beispielsweise auf diese Weise entstanden. Weiterhin existiert ein Werkzeug, um zwischen Kurven linear zu interpolieren, und um eine Liste von
Punkten einer Bézierkurve als Kontrollpolygon zu übergeben.
t_GmMapCurveEval
t_GmMapCurveMap
t_GmMapCurveTangent
t_GmMapCurveXNormal
t_GmMapCurveYNormal
t_GmMapSurfaceEval
t_GmMapSurfaceMap
t_GmMapSurfaceNormal
t_GmMapSurfaceSTangent
t_GmMapSurfaceTTangent
t_GmMapCurvesToCoonsSurf
t_GmMapInterpolateCurves
t_GmMapPointsToBezier
t_CurveMap
t_SurfaceMap
Um Kurven und Flächen im interaktiven Viewer zu betrachten, gibt es die folgenden Abbildungen, die ein Sampling durchführen und deren Rückgabewert ein BRep ist. Dies ist als ein Beispiel
für beliebige Tesselierungsklassen, auch eine adaptive Triangulierung einer Oberfläche läßt sich
als Abbildung auffassen.
t_GmMapSampleCurveBRep
t_GmMapSampleSurfBRep
t_GmMapShowSceneSimple
t_GmObject
5.2. IMPLEMENTATION IN HASKELL
75
Das MRT-Objekt t_GmObject kann die approximative Darstellung auf das Sampling zurückführen. Eine Lösung für das Strahlschnittproblem mit allgemeinen parametrischen Flächen, so
wie sie beim Generativen Modellieren entstehen, ist ein wesentlich härteres Problem. Obwohl im
Laufe der Entwicklung verschiedene Algorithmen getestet wurden, ist keine in allen Fällen zufriedenstellende Lösung gefunden worden. – Das Grundproblem ist das Lösen einer impliziten Gleichung durch ein Iterationsverfahren. Dies wurde zwar erfolgreich implementiert, das hauptsächliche Problem ist aber das Finden eines geeigneten Startpunktes. Es muß ein Punkt im Parameterraum (ustart ; vstart ; tstart ) gefunden werden, der durch Iteration garantiert zu dem Schnittpunkt von
Fläche und Strahl (uschnitt; vschnitt; tschnitt) konvergiert. Es wurden eine Reihe von Startpunktheuristiken ausprobiert, doch keine lieferte ein robustes Lösungsverhalten. Um aber sicherzugehen,
daß sich ein bestimmter Startpunkt nicht in einem lokalen Minimum der Zielfunktion verfängt,
benötigt man letztendlich Inklusionsfunktionen wie bei Snyders Ansatz.
5.2
Implementation in Haskell
5.2.1
Basisklassen
Vector3
Curve
Surface
Transform
Dies sind alle Klassen, die notwendig sind, um in Haskell generativ zu modellieren. Spezielle Implementationen von Kurven oder Flächen sind dagegen in Haskell-Terminologie Instanzen. Diese
müssen jedoch nur die jeweiligen Auswertungsfunktionen überschreiben, die restliche Funktionalität kann mit Defaultimplementationen bereits von der Klasse generisch erledigt werden. Dazu
zählt insbesondere die Ableitungsberechnung, die nach dem eingangs geschilderten Verfahren von
der Basisklasse in numerischer Näherung durchgeführt wird. Ähnlich wie in C++ kann eine Instanz diese Funktionen aber auch selber überschreiben, wenn es eine effizientere oder genauere
Möglichkeit dazu in einem Spezialfall gibt. In diesem Sinne sind sämtliche Funktionen, die in
einer Haskell-Klasse definiert werden, in OOP-Terminologie virtuelle Funktionen.
Von einem Überschreiben von Ableitungen wurde für die Instanzen in der vorliegenden Implementation allerdings Abstand genommen, vielmehr wurde der Schwerpunkt auf eine reichhaltige
Bibliothek von Operatoren gelegt.
Bei der Vorstellung von Haskell in 4.2.1 wurde deutlich, warum die Implementation der Generativen Modellierung in einer funktionalen Sprache wesentlich knapper ausfallen kann als in C++:
Die Grundkonzepte, die bei der C++-Implementation den Kern von Genmod ausmachen, sind bereits integraler Bestandteil der Sprache. – Der Genmod-Kern in C++ ist auf große Allgemeinheit
ausgelegt, um den elementaren Anforderungen des objektorientierten Designs zu genügen, insbesondere mußte auf die Implementation der Snyderschen Operatormethode zur Bestimmung einer
Inklusionsfunktion eines Operators verzichtet werden. - Die Haskell-Implementation kann aber
gerade dies zum Beispiel für die Operatormethode “Bestimmung der Ableitungsfunktion” leisten.
Anders als in GENMOD ist dazu keine Unterscheidung zwischen Operatoren und Operatormethoden notwendig! - In einer funktionalen Sprache ist es durchaus möglich, daß ein Objekt eine
Funktion liefert, die dynamisch zusammengebaut wird, zum Beispiel eine Ableitungsfunktion.
Für die Bestimmung der Ableitung der Summe zweier Kurven wäre daher folgendes denkbar:
data (Curve c, Curve d) => CurveAdd c d = CrvAdd c d
instance (Curve c, Curve d) => Curve (CurveAdd c d) where
derivative (CrvAdd crv1 crv2) =
CrvAdd (derivative crv1) (derivative crv2)
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
76
Der Rückgabewert von derivative ist also selber wieder eine Kurve. – Grenzen findet ein solcher Mechanismus allerdings in der Verwendung von allgemeinen Funktionen als Kurven, Flächen
und Transformationen wie weiter unten in CrvFct, SrfFct und TrnFct vorgestellt. Daher müßte bei
der Einführung weiterer Operatormethoden untersucht werden, ob es entweder möglich ist, ohne
diese Instanzen auszukommen, oder eine Operatormethode für diese Instanzen ebenfalls generisch
zu implementieren.
In den folgenden Unterabschnitten werden zunächst die Klassen im einzelnen vorgestellt und
dann die implementierten Instanzen aufgezählt. Die Implementation dieser Instanzen besteht jeweils nur aus wenigen Zeilen Haskell-Code, daher wurde es möglich, die gesamte Implementation
im Anhang A auf neun Seiten unterzubringen. An dieser Stelle soll im wesentlichen ein Überblick
gegeben werden.
5.2.1.1
Vektoren
add
sub
prod
cross
mult
x
y
z
normalize
absLen
Die Funktionen prod und mult sind Skalarprodukt und skalare Multiplikation. Der Vollständigkeit
halber sei noch der Konstruktor erwähnt:
data Vector3 = V3 Float Float Float
5.2.1.2
Kurvenklasse
class Curve c
tangent
mainNormal
biNormal
evalCurve
derivOne
derivTwo
diff_1
diff_2
where
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
Float
Float
Float
Float
Float
Float
Float
Float
->
->
->
->
->
->
->
->
Vector3
Vector3
Vector3
Vector3
Vector3
Vector3
Vector3
Vector3
diff_1
crv t = sub (evalCurve crv (t+diffQuot_h))
(evalCurve crv (t-diffQuot_h))
diff_2
crv t = sub (add (evalCurve crv (t+diffQuot_h))
(evalCurve crv (t-diffQuot_h)))
(add p p) where p=evalCurve crv t
derivOne
crv t = mult diffQuot_2h (diff_1 crv t)
derivTwo
crv t = mult diffQuot_h2 (diff_2 crv t)
tangent
crv t = normalize (diff_1 crv t)
biNormal
crv t = normalize (cross (diff_1 crv t) (diff_2 crv t))
mainNormal crv t = normalize (cross (cross d1 d2) d1)
where d1 = diff_1 crv t
d2 = diff_2 crv t
Die Funktionen diff_1 und diff_2 sind dabei aus Effizienzgründen vorhanden, tangent spart
durch die Verwendung von diff_1 eine skalare Multiplikation.
Die Konstanten diffQuot_h, diffQuot_2h und diffQuot_h2 für die Differenzenquotienten stehen
1
für die Zahlen h, 2h
und h12 .
5.2. IMPLEMENTATION IN HASKELL
77
Es wird angenommen, daß die Auswertung einer Kurve über dem Standardintervall [0; 1] stattfindet, eine explizite Überprüfung wird nicht vorgenommen. Zudem gilt für alle implementierten
Kurven, daß sie auch außerhalb des Standardintervalls definiert sind.
5.2.1.3
Flächenklasse
class Surface
tangentU
tangentV
normal
evalSurf
tangentU
tangentV
normal
s where
:: s ->
:: s ->
:: s ->
:: s ->
srf u v
srf u v
srf u v
Float -> Float -> Vector3
Float -> Float -> Vector3
Float -> Float -> Vector3
Float -> Float -> Vector3
= tangent (CrvKeepV srf v) u
= tangent (CrvKeepU srf u) v
= normalize $ cross (tangentU srf u v)
(tangentV srf u v)
Die Implementation greift hier auf zwei Instanzen der Kurvenklasse zurück, die einen Parameter fixieren; die Ableitungsvektoren dieser Kurven sind dann gerade die partiellen Ableitungen
dieser Kurven.
Die Auswertung findet auch hier über dem Standardintervall [0; 1] [0; 1] statt, ebenfalls ohne
Überprüfung. Auch hier können die implementierten Klassen auch außerhalb des Standardintervalls benutzt werden.
5.2.1.4
Transformationsklasse
class Transform t where
evalTrans :: t -> Vector3 -> Vector3
Transformationen besitzen die allgemeinste Definition. Auf die Berechnung der Ableitungen
wurde hier verzichtet, weil Transformationen im allgemeinen nur in statischer Weise benutzt werden. – Transformationen sind für alle dreidimensionalen Vektoren definiert.
5.2.2
Instanzen der Basisklassen
CrvX
CrvSegment
CrvEllipse
CrvCircle
CrvBezier
CrvReparam
CrvReverse
CrvKeepU
CrvKeepV
CrvFct
CrvTransform
CrvInSurf
CrvInterpol
Identische X-Kurve, Strecke, Ellipse gegeben durch Mittelpunkt und Richtungsvektoren, Standardkreis und kubische Bézierkurve sind übliche Kurvenprimitive. Die Auswertungsrichtung einer Kurve kann durch die Transformation t 7! (1 , t ) umgedreht werden, Reparametrisierung einer
Kurve durch eine andere bedeutet:
evalCurve (CrvReparam f g) t = evalCurve f (x (evalCurve g t))
Zudem kann eine beliebige Funktion der Signatur R ! R3 in eine Kurve umgewandelt werden,
und eine Transformation angewandt auf eine Kurve ergibt wieder eine Kurve. Die letzten beiden
Klassen dienen dazu, eine Kurve in den Parameterraum einer Fläche zu legen, und um die lineare
Interpolation zu einem festen Interpolationswert zwischen zwei gegebenen Kurven als neue Kurve
zu gewinnen.
78
KAPITEL 5. IMPLEMENTATIONEN VON GENMOD IN C++ UND HASKELL
SrfXY
SrfSphere
SrfSphereSegment
SrfBezier
SrfInterpolCrv
SrfCoons
SrfRailProd
SrfWireProd
SrfFct
SrfTransform
SrfParamCrv
Identische xy-Ebene, Kugel in Polarkoordinaten und viereckiges Kugelsechstel sind übliche
Primitive; eine Abbildung mehrerer Kugelsechstel findet man in Beispiel 6.2.4 auf Seite 90. Bikubische Bézier-Flächen werden in eleganter Weise definiert, indem man vier kubische Bézierkurven
direkt aus den Kontrollpunkten gewinnt, diese auswertet und daraus eine neue Bézierkurve konstruiert. Dieses Vorgehen ist vergleichbar mit den Tensorflächen im cns-Paket, aber nicht so allgemein. Lineare Interpolationsflächen zwischen Kurven und Coons-Patches sind Werkzeuge, um
Kurven zu Flächen zu kombinieren. Dazu gehören auch rail product und wire product, die in Beispiel 6.2.7 dargestellt werden. Schließlich existieren noch die Darstellung einer Funktion R2 ! R3
als Fläche und die Transformation einer Fläche, die wieder eine Fläche ist. Zudem kann man auch
eine Kurve, die zusätzlich von einem reellen Parameter abhängig ist, als Fläche auffassen. Der
Konstruktor erhält also eine Funktion:
data (Curve c) => SurfParamCrv c = SrfParamCrv (Float->c)
TrnCmps
TrnFct
TrnScale
TrnTranslate
TrnRotXY
TrnRotYZ
TrnRotZX
Neben den Standardtransformationen existiert die Darstellung einer Funktion R3 ! R3 als
Transformation, und die Komposition zweier Transformationen liefert wieder eine Transformation. Transformationen können also zunächst nicht per Infix-Operator komponiert werden, sondern
werden hintereinandergeschrieben:
evalTrans (TrnCmps trn2 trn1) p =
evalTrans trn2 (evalTrans trn1 p)
Die Rotationstransformationen erhalten einen Drehwinkel nicht in Grad oder Radian, sondern
ebenfalls aus [0; 1], dem allgegenwärtigen reellen Standardintervall. Die Skalierung erhält einen
dreidimensionalen Vektor, der die Streckung in die Koordinatenrichtungen beschreibt, man erhält
die identische Skalierung also folgendermaßen:
trnId = TrnTranslate (V3 1 1 1)
line
heightfield
showSurface
showCurve
showSurfaces
showCurves
Neben der bereits in 4.2.2 gezeigten Strecke gibt es als Hilfsfunktion das Höhenfeld, das ein
regelmäßiges Gitter von 4 4 Punkten auf der xy-Ebene verteilt und nur die sechzehn Höhenwerte
übergeben bekommt, die es braucht, um daraus eine bikubische Bézierfläche zu machen. Das
Höhenfeld existiert in zwei Versionen, einmal bedeckt es [,1; 1] [,1; 1] auf der Grundebene
und als heightfieldNrm das Standardintervall [0; 1] [0; 1].
Die Funktionen showCurve und showSurface führen ein regelmäßiges Sampling durch, führte
man eine echten MRT-Integration von Haskell-Genmod durch, wäre denkbar, daß sie zum Überführen einer Kurve oder einer Fläche in ein MRT-Objekt dienen, das ähnlich wie beim GenmodPaket nach wie vor Zugang zu der Auswertungsfunktion besitzt, um Approximationen beliebig
zu verfeinern und Strahlschnitte durchführen zu können. – Die zweiten Versionen, showCurves
und showSurfaces bekommen jeweils eine Kurve bzw. eine Fläche, die noch von einem zusätzlichen reellen Parameter abhängig sind. Für diesen Parameter erfolgt ebenfalls ein Sampling, das
ermöglicht die Ausgabe einer Schar von Kurven oder Flächen wie beim Zaun-Beispiel 6.2.5.
Kapitel 6
Beispiele und Erfahrungsbericht
In diesem Kapitel sollen konkrete Beispiele für Generatives Modellieren vorgestellt werden.
Es ist für das Verständnis der theoretischen Überlegungen in den vorangegangenen Kapiteln
wichtig, deutlich zu machen, daß hier ein praktischer Modellieransatz erarbeitet wurde, der beim
Modellieren im Großen einen wirklichen Fortschritt gegenüber der existierenden Technologie darstellt. Man muß von den einfachen Beispielen einzelner Flächen abstrahieren, um eine Vorstellung
davon zu bekommen, wie ein Modellierer bei realistischen Problemstellungen von einem generativen Modeller profitiert. Ein Stuhl, ein Lattenzaun, eine Wendeltreppe werden nicht konkret
modelliert und dann nur noch per cut&paste weiterverwendet, sondern ein Modellierer macht sich
den Aufbau eines Modells klar, überlegt sich, welche Größen variabel und welche fest sind, um
daraus schließlich ein Werkzeug machen, das fortan in konkreten Konstruktionen als parametrisiertes Modell verwendet werden kann.
Die Genmod-Beispiele zeigen die Tragfähigkeit der C++-Implementierung, das Funktionieren
des implementierten Abbildungsmodells und der parametrischen Kurven und Flächen, auch wenn
hier nur auf C++-Ebene modelliert worden ist. Die Haskell-Beispiele geben einen Eindruck davon,
wie eine Beschreibung in einer Datei für ein gegebenes Beispiel aussehen könnte. Gleichzeitig
vermittelt die knappe textuelle Beschreibung, die in Haskell möglich ist, einen Eindruck davon,
welche Schritte in einem interaktiven Modeller denkbar sind, um eines der Beispielmodelle zu
konstruieren, deren Beschreibung hier gegeben wird.
6.1 Genmod-Beispiele
Das Vorgehen bei den Beispielen ist so, daß C++-Funktionen geschrieben wurden, die Operatoren
repräsentieren. Die Abbildungen in diesen Funktionen produzieren Objekte vom Typ parametrische Kurve bzw. Fläche. Um diese darzustellen, werden daraus t_GmObject-Objekte gemacht, die
von der MRT-Basisklasse t_SurfaceObject abgeleitet sind und folglich in einen Szenengraphen
eingetragen und dargestellt werden können.
Diese Funktionen wurden dann in einem zweiten Schritt in Klassen verpackt, die man von der
Basisklasse t_GmDemo ableitet. Diese stellt Funktionen checkinCurve() und checkinSurface() für
die produzierten Kurven und Flächen zur Verfügung, eine bestimmte Demo besteht dann aus den
dazu erzeugten MRT-Objekten. Die entsprechenden Zeilen in einer msd-Datei lauten dann zum
Beispiel:
GENMOD
GENMOD
2
2
"Sinwave" 0 // spiral
"Sinwave" 1 // wave
79
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
80
6.1.1
Sinusfläche mit Spirale
Hier wird einmal eine Sinusfläche und zum anderen eine Spirale dargestellt, nach diesen Formeln:
0
1
4 (s , 0 5)
sinwave(s t ) = @ sin(2 π (1 , s)) + cos(2 π t ) A
0 cos(12 t ) 1
A
spiral(t ) = @
t
;
;
4 (t , 0; 5)
;
sin(12 t )
Wie erläutert erhält eine zusammengesetzte Abbildung als ersten Parameter die letzte auszuwertende Abbildung eines DAGs. Dann werden deren Eingabeabbildungen zurückverfolgt und
so kann der gesamte DAG kopiert werden. – Dieser Prozeß kommt dadurch zum Halten, daß
der Beginn des DAG aus konstanten Abbildungen besteht, die selber keine Eingabeabbildungen
haben. Das bedeutet aber, daß die zusammengesetzte Abbildung keine eigenen Parameter mehr
hat. Um dies zu verhindern, kann man einer t_GmMapCompose noch weitere Parameter anfügen.
Diese werden dann als die Eingaben einer zusammengesetzten Abbildung benutzt. Im abgedruckten Beispiel übernehmen die reellen konstanten Abbildungen s und t die Rolle der Parameter
der Fläche sinwave, sie werden compose als Eingabeparameter übergeben. Die zusammengesetzte Abbildung bildet somit zwei reelle Zahlen auf einen Vektor ab und kann als solche in eine
t_SurfaceMap verpackt werden. Dabei wird ein Rebinding durchgeführt, denn die Memberfunktion Pt& evaluate(t_Real s, t_Real t) der parametrischen Fläche muß die Eingabe der zusammengesetzten Abbildung auf die eigenen Parameter umlenken. Die ursprünglichen Parameter s und t
spielen also die Rolle von Dummy-Variablen, die nur dem Zweck dienen, eine korrekt gebundene
zusammengesetzte Abbildung zu erzeugen.
void t_GmDemoSinwave::execute()
{
t_Real realFour=4.0, realMtwo=-2.0,
t_GmMapRealConst
s,t;
t_GmMapRealConst
t_GmMapRealConst
t_GmMapRealConst
t_GmMapRealMult
t_GmMapRealMult
t_GmMapRealMult
t_GmMapRealMult
t_GmMapRealAdd
t_GmMapRealAdd
t_GmMapRealMinus
t_GmMapRealSin
realTwopi=2.0*M_PI;
four(&realFour);
mtwo(&realMtwo);
twopi(&realTwopi);
fourS;
fourS
fourT;
fourT
s2pi;
s2pi
t2pi;
t2pi
x;
x
z;
z
tpiMs2pi; tpiMs2pi
sinus;
sinus
(t)(four);
(s)(four);
(t)(twopi);
(s)(twopi);
(mtwo)(fourS);
(mtwo)(fourT);
(twopi)(s2pi);
(tpiMs2pi);
6.1. GENMOD-BEISPIELE
81
t_GmMapRealCos
t_GmMapRealAdd
t_GmMapPt3DConcatReal
t_GmMapCompose
cosinus;
y;
con;
compose;
cosinus
y
con
compose
(t2pi);
(sinus)(cosinus);
(x)(y)(z);
(con)(s)(t);
t_GmMapSurfaceMap
sinwave;
sinwave
(compose);
t_Real realCrvFakt = 12.0;
t_GmMapRealConst
t_GmMapRealMult
t_GmMapRealSin
t_GmMapRealCos
t_GmMapPt3DConcatReal
t_GmMapCompose
t_GmMapCurveMap
crvFakt(&realCrvFakt);
tScal;
tScal
(t)(crvFakt);
sint;
sint
(tScal);
cost;
cost
(tScal);
point;
point
(cost)(t)(sint);
cmp2;
cmp2
(point)(t);
spiral;
spiral
(cmp2);
checkinCurve(curveset);
checkinSurface(sinwave);
}
6.1.2
Morphing
Das Grundwerkzeug, auf dem viele Ideen in der Geometrischen Modellierung beruhen, ist die
Strecke als affine Konvexkombination zweier Raumpunkte:
strecke pq (t ) = (1 , t ) p + t q
Die Bedeutung dieses Konzeptes wird dadurch deutlich, daß sowohl Bézierkurven als auch
Splines, wie auch jede lineare Iterpolation zwischen Kurven oder Flächen bis hin zur vierdimensionalen Quaternioneninterpolation letztendlich auf dieser Formel beruhen. – Die Strecke ermöglicht sowohl ein dreidimensionales wie auch ein zweidimensionales Morphing zwischen Flächen.
Beides wird in diesem Beispiel demonstriert.
Zum einen ist ein Morph zwischen der Kugel in Polarkoordinaten und einer bikubischen Freiformfläche abgebildet. Die Kugel wird dabei durch Sinus- und Kosinusausdrücke definiert, das
Bézierpatch dagegen ist eine einfache Polynomfläche im Raum, benötigt also keinerlei transzendente Funktionen. Die Generative Modellierung ermöglicht es aber, beide Flächen in gleicher
Weise zu behandeln, und zwar dadurch, daß die Information vorhanden ist, daß beide Objekte
von t_SurfaceParam3D abgeleitet sind. Die Interpolationsfläche kann nun mit einem Parameter
c 2 [0; 1] so ausgedrückt werden:
morphc(u; v) = (1 , c) bezier(u; v) + c sphere(u; v)
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
82
Zum anderen sind Momentaufnahmen eines zweidimensionalen Morphings abgebildet. Die
eine Fläche ist eine bikubische Bézierfläche mit quadratischem Rand, deren innere vier Kontrollpunkte in charakteristischer Weise verdreht sind. Die andere Fläche ist ein einfaches Quadrat in
der xz-Ebene. Interpoliert man nun linear zwischen beiden Flächen, so erhält man zu jedem Interpolationsparameter eine quadratische Fläche. Die äußere Form bleibt also gleich, aber Punkte für
einen bestimmten (u; v)-Wert wandern innerhalb der Fläche. Klebt man ein Bild auf diese Fläche,
so kann man die Verformung des Bildes bei der Interpolation beobachten. Nach diesem Prinzip
funktioniert auch das wirkliche Morphing in kommerziellen Systemen, wobei ein dichteres Netz
von Kontrollpunkten benutzt wird. – Das einzige Werkzeug, das man zur Definition der Morphs
braucht, ist die C++-Funktion interpolate, die in 4.1.2 gezeigt wurde.
6.1.3
Sweep-Werkzeug
Dieses Beispiel realisiert eine skalierte Profilsweepfläche und ist ein erstes Beispiel für die Definition eines Metashapes in Genmod. Es wird dabei eine Sehne, eine Skalierkurve und eine
Profilkurve verwandt. Die Skalierkurve f (s) ist eine ebene Kurve, die zu s 2 [0; 1] einen Punkt
( f x (s); f y(s)) 2 R2 liefert. Die Profilkurve p(t ) ist eine beliebige ebene Kurve, die mit f (s) skaliert
wird. Zu jedem s erhält man so eine skalierte Profilkurve ps (t ) = ( fx(s) px (t ); fy(s) py (t )).
Die Sehne c(s) ist eine beliebige Raumkurve. Die Haupt- und die Binormale des Frenet’schen
Dreibeins spannen an jedem Kurvenpunkt die Normalebene der Kurve auf und können als Basisvektoren dieser Ebene betrachtet werden. In diese Ebene wird nun die skalierte Profilkurve ps (t )
gelegt, die Gesamtheit dieser Kurven für alle s ergibt damit eine Fläche a. Betrachtet man Hauptund Binormale als Funktionale, die angewendet auf eine Kurve wiederum eine Kurve ergeben, so
erhält man:
a(s; t ) = c(s)
+
fx (s) px(t ) hauptnormale(c)(s)
+
fy (s) py(t ) binormale(c)(s)
Die Haupt- und Binormale werden von Abbildungen mit den suggestiven Namen t_GmMapCurveXNormal bzw. t_GmMapCurveYNormal erzeugt.
t_GmMapCompose t_GmDemoScaledProfileSweep::sweep(t_GmMAP& s,
t_GmMAP& t, t_GmMAP& sehne, t_GmMAP& scaler, t_GmMAP& prof)
{
long c0=0,c1=1;
t_GmMapLongConst
cX(&c0);
t_GmMapLongConst
cY(&c1);
t_GmMapCurveEval
sehnS;
sehnS
(sehne)(s);
t_GmMapCurveXNormal sehnXNormS; sehnXNormS (sehne)(s);
t_GmMapCurveYNormal sehnYNormS; sehnYNormS (sehne)(s);
6.1. GENMOD-BEISPIELE
83
t_GmMapCurveEval
t_GmMapCurveEval
scalerS;
profT;
scalerS
profT
(scaler)(s);
(prof)(t);
t_GmMapPt3DCoord
t_GmMapPt3DCoord
t_GmMapPt3DCoord
t_GmMapPt3DCoord
scalerSX;
scalerSY;
profTX;
profTY;
scalerSX
scalerSY
profTX
profTY
(scalerS)(cX);
(scalerS)(cY);
(profT)(cX);
(profT)(cY);
t_GmMapRealMult
t_GmMapRealMult
t_GmMapPt3DScalMult
t_GmMapPt3DScalMult
factorX;
factorY;
scalXNormS;
scalYNormS;
factorX
factorY
scalXNormS
scalYNormS
(scalerSX)(profTX);
(scalerSY)(profyTY);
(factorX)(sehnXNormS);
(factorY)(sehnYNormS);
t_GmMapPt3DAdd
t_GmMapPt3DAdd
t_GmMapCompose
sehnSPlsX; sehnSPlsX (sehnS)(scalXNormS);
sehnSPlsXY; sehnSPlsXY (sehnSPlsX)(scalYNormS);
result;
result
(sehnSPlsXY)(s)(t);
return result;
}
6.1.4
Bézier-Blending
Das Werkzeug zum Bézier-Blending ist ein weiteres Beispiel für einen Metashape. Es werden dabei vier Raumkurven b0 , r0, r1 und b1 in dieser Reihenfolge erwartet. Wertet man alle vier Kurven
am gleichen Parameter t aus, so erhält man vier Punkte im Raum. Diese vier Punkte werden dann
als Kontrollpunkte einer kubischen Bézierkurve aufgefaßt, die Gesamtheit der Kurven ergibt auch
hier eine Fläche:
a(s; t ) = bezier[b0(s); b0(s) + r0(s); b1(s) + r1(s); b1(s)] (t )
Der Grund für die Benennung ist die Eigenschaft von Bézierkurven, daß zum einen die Endpunkte interpoliert werden und zum anderen die Richtung der Tangente in diesen Punkten von
den benachbarten inneren Kontrollpunkten vorgegeben wird. Das bedeutet, das Bézier-BlendingWerkzeug ist eine Fläche, wo zwei Randkurven vorgegeben werden und zwei innere Kurven, mit
denen man die Richtung einer partiellen Ableitung der Fläche spezifizieren kann. In [Far90] wird
daher von tangent ribbon curves gesprochen, also Gummibändern, die die Fläche intuitiv gesprochen in eine Richtung zerren.
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
84
t_GmMapCompose t_GmDemoBlendTool::blend(t_GmMAP& s, t_GmMAP& t,
t_GmMAP& border0,t_GmMAP& ribbon0,t_GmMAP& ribbon1,t_GmMAP& border1)
{
t_GmMapCurveEval
b0Eval; b0Eval (border0)(s);
t_GmMapCurveEval
b1Eval; b1Eval (border1)(s);
t_GmMapCurveEval
r0Eval; r0Eval (ribbon0)(s);
t_GmMapCurveEval
r1Eval; r1Eval (ribbon1)(s);
t_GmMapPt3DAdd
b0r0;
b0r0
(b0Eval)(r0Eval);
t_GmMapPt3DAdd
b1r1;
b1r1
(b1Eval)(r1Eval);
t_GmMapPointsToBezier mybez;
mybez
(b0Eval)(b0r0)(b1r1)(b1Eval);
t_GmMapCurveEval
bezEval; bezEval (mybez)(t);
t_GmMapCompose
return result;
result;
result
(bezEval)(s)(t);
}
6.1.5
Bézier-Blending mit Kugelnormale
Dies ist ein Beispiel für die Anwendung des Bézier-Blending-Werkzeuges, und ein Beispiel für
ein parametrisiertes Modell. Spezifiziert man eine eben Kurve c(t ), die sich im Standardintervall
[0; 1] [0; 1] bewegt, so kann man dieses als das Parameterintervall einer parametrischen Fläche
auffassen, durch Komposition erhält man eine Raumkurve. Diese Kurve kann man zusätzlich von
einem Parameter abhängig machen, in diesem Fall der “Höhe” der ebenen Kurve im Parameterintervall. Im Beispiel wird als Fläche eine Kugel in Polarkoordinaten verwandt, insgesamt erhält
man:
bh (t ) = sphere(cx (t ); cy(t ) + h)
Diese Kurve dient dann als eine Randkurve für das Blending-Werkzeug, die andere Randkurve
wird erzeugt, indem dieselbe Konstruktion noch einmal gespiegelt durchgeführt wird. Um die inneren Kurven, die tangent ribbon curves, zu erzeugen, wird an am Kurvenpunkt c(t ) zusätzlich die
Normale der Kugel bestimmt, die inneren Kurven liegen damit auf einer Offsetfläche der Kugel,
also auf einer Kugel mit größerem Radius.
rh (t ) = (normale(sphere))(cx (t ); cy(t ) + h)
Betrachtet man die resultierende Fläche, so kann man sie als ein relativ kompliziertes Modell
betrachten, das über eine einzelne reelle Zahl parametrisiert ist, und zwar die Höhe der Kurve im
Parameterintervall der Kugel:
ah(s; t ) = bezier[b0h (s); b0h(s) + r0h (t ); b1h(s) + r1h (t ); b1h(s)](t )
Damit ist demonstriert, auf welche Weise die Trennung von Modell und Eingabedaten in Genmod realisiert werden kann.
6.1. GENMOD-BEISPIELE
6.1.6
85
Bézier-Blending mit Kugeltangente
Das vorige Beispiel kann man variieren, indem die inneren Kurven des Bézier-Blending-Werkzeuges nicht durch einen Offset in Normalrichtung der Kugel erzeugt werden, sondern in der
Tangentialebene am Kurvenpunkt liegen. Das erreicht man, indem man die Binormale der komponierten Raumkurve betrachtet, die Basisvektoren des Tangentialraums der Kugel berechnet, und
die Binormale in diese Ebene projiziert und schließlich normiert. Dies kann man sich so vorstellen, als liefe man eine Kurve auf einer Kugel entlang und schaute die ganze Zeit genau nach links,
respektive rechts auf der gespiegelten Kugel. Einen bestimmten Abstand in dieser Richtung entfernt findet man dann den entsprechenden Kurvenpunkt der zugehörigen tangent ribbon curve.
Man erhält die inneren Kurven des Bézier-Blending-Werkzeuges also auf folgende Weise:
rh (t )
=
bnh (t ) , projekt(nh (t ); bnh (t ))
bnh(t )
=
binormale(sphere(cx (t ); cy(t ) + h))
nh (t )
=
normale(sphere)(cx(t ); cy(t ) + h)
mit
Diese Berechnung wird im folgenden Programmsegment durchgeführt, curve0 ist dabei die ebene
Kurve, p_curve0 die parameterabhängige Raumkurve nach Komposition mit der Kugel, im Code
mit sphere bezeichnet. Das Modell besitzt als Eingabeparameter immernoch die Höhe der Kurve
im Parameterraum.
long
t0_coord0Long=0;
long
t0_coord1Long=1;
t_Real t0_scalReal=1.9;
t_GmMapLongConst
t_GmMapLongConst
t_GmMapRealConst
t_GmMapCurveEval
t_GmMapPt3DCoord
t_GmMapPt3DCoord
t_GmMapSurfaceNormal
t_GmMapCurveYNormal
t_GmMapPt3DProject
t_GmMapPt3DMinus
t_GmMapPt3DNormalize
t_GmMapPt3DScalMult
t_GmMapCompose
t_GmMapCurveMap
t0_c0(&t0_coord0Long);
// t0_c0=0
t0_c1(&t0_coord1Long);
// t0_c1=1
t0_scal(&t0_scalReal);
// t0_scal=1.9
t0_pEval; t0_pEval(p_curve0)(t);
t0_pX;
t0_pX(t0_pEval)(t0_c0);
t0_pY;
t0_pY(t0_pEval)(t0_c1);
t0_sphN; t0_sphN(sphere0)(t0_pX)(t0_pY);
t0_cLeft; t0_cLeft(curve0)(t);
t0_prj;
t0_prj(t0_sphN)(t0_cLeft);
t0_leftV; t0_leftV(t0_cLeft)(t0_prj);
t0_left; t0_left(t0_leftV);
t0_leftS; t0_leftS(t0_scal)(t0_left);
t0_map;
t0_map(t0_leftS)(t);
tangent0; tangent0(t0_map);
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
86
6.1.7
Stuhl
Als abschließendes Beispiel für die Verwendung von Genmod ist mit dem Stuhl ein komplexes parametrisiertes Modell entstanden. Der Stuhl wird dabei als Metashape modelliert, das als Eingabe
die abgebildeten Skelettkurven erhält. Als “Konstanten” enthält der Stuhl-Operator dabei Profilund Skalierkurven für die Lehne, für die Beine und für die Seiten der Sitzfläche. Diese Kurven
werden mit den Eingabekurven kombiniert und zum einen dem scaled profile sweep-Werkzeug
übergeben, das daraus die Lehne erzeugt. Die Sitzseiten werden durch einen Sweep ohne Transformation des Profils erzeugt, einfach durch Entlangziehen an der Randkurve, und die Stuhlbeine
werden duch einen skalierten Sweep ohne Ebenentransformation generiert, die Konstruktionsebene ist parallel zum Boden. Für die Konstruktion der Sitzfläche selber werden zunächst aus den
Sitzseitenflächen die Randkurven gewonnen, was man durch Festhalten eines Parameters der Fläche erreicht. Diese Randkurven werden schließlich einer Coons-Fläche übergeben, die sie bilinear
verblendet.
Damit ist gezeigt, daß mit Hilfe des Genmod-Paketes die wesentlichen Ziele der Generativen
Modellierung erreicht wurden.
Der Genmod-Ansatz ist tragfähig
Es können nicht nur kleine Demonstrationsbeispiele modelliert werden, sondern die implementierten Datenstrukturen ermöglichen bereits die Modellierung von praktisch relevanten
Modellen.
Parametrisierbarkeit ermöglicht Modelliereffizienz
Ein Benutzer kann allein durch Auswechseln der definierenden Kurven eine ganze Schar
von Stühlen erzeugen. Kurven können selber wieder parameterabhängig sein, so kann man
wie im Beispiel 6.1.2 ein kontinuierliches Morphing zwischen verschiedenen Stuhlklassen
durchführen. Des weiteren kann man einen Stuhl in den charakteristischen Maßen parametrisieren, die Höhe der Lehne, des Sitzes, die Breite der Sitzfläche und so weiter. Damit
ist der Nutzen der Isolierbarkeit von Freiheitsgraden, die mit der Generativen Modellierung
möglich geworden ist, an einem praktischen Beispiel demonstriert.
6.2. HASKELL-BEISPIELE
6.2
87
Haskell-Beispiele
Im folgenden wird eine Reihe von Beispielen für die Benutzung der vorgestellten Haskell-Klassen
und -Instanzen näher besprochen. Diese Beispiele stellen im Grunde die beste Motivation für
die Realisierung der Generativen Modellierung in einer funktionalen Sprache dar, denn bei der
Betrachtung der wenigen Zeilen Code wird deutlich, wie einfach die Beschreibung komplexer
Modelle sein kann. Zudem erfüllen sie dabei durch die Übersichtlichkeit jede Forderung nach
Änderbarkeit; es wird auch darauf eingegangen, wie ein Benutzer die Größen, die er nachträglich
ändern können möchte, zu Parametern eines Modells machen kann.
In manchen Beispielen entfällt der Abdruck der länglichen main-Funktion einer Anwendung,
wenn sie nichts Erhellendes beiträgt.
6.2.1
Frenet-Rahmen als Kurven
Dieses Beispiel demonstriert, wie in einem Anwenderprogrammen eine neue Flächenklasse definiert wird, um die Werkzeugbibliothek um eine oft gebrauchte Konstruktione zu ergänzen. Die
Klasse SrfOffsetIntp wurde bereits in Kapitel 4.2.2 vorgestellt, zudem wird hier eine parametrisierte Konstruktion verwendet: Die Funktion frenet_m t gibt für jedes t 2 [0; 1] eine Strecke
zwischen der Kurve und ihrer Hauptnormalenkurve zurück. Mit dem showCurves-Aufruf wird
diese Funktion an elf Stellen ausgewertet und die entstehenden Kurven – Strecken – ausgegeben.
p0
p1
p2
p3
=
=
=
=
V3 (-2.0) 0.0 (-1.0)
V3 (-1.0) 0.8
0.0
V3
1.0 0.8
2.0
V3
2.0 0.0 (-1.0)
data (Curve c)=> SurfOffsetIntp c = SrfOffsetIntp c (c->Float->Vector3)
instance (Curve c) => Surface (SurfOffsetIntp c) where
evalSurf (SrfOffsetIntp path fct) u v = evalSurf fctSrf u v
where fctSrf = SrfInterpolCrv path fctPath
fctPath = CrvFct (\t -> add (evalCurve path t)
(fct path t))
path
= CrvBezier p0 p1 p2 p3
pathNormal
= CrvFct (\t-> add (evalCurve path t) (mainNormal path t))
pathBiNormal = CrvFct (\t-> add (evalCurve path t) (biNormal
path t))
srfNormal
= SrfInterpolCrv path pathNormal
srfBiNormal = SrfInterpolCrv path pathBiNormal
frenet_m t
= CrvSegment (evalCurve path t) (evalCurve pathNormal
t)
frenet_b t
= CrvSegment (evalCurve path t) (evalCurve pathBiNormal t)
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
88
main = putStr $
(showCurve
++ (showCurve
++ (showCurve
++ (showSurface
++ (showSurface
++ (showCurves
++ (showCurves
6.2.2
"path"
10
"pathNormal"
60
"pathBiNormal" 60
"srfNormal"
8
"srfBiNormal"
8
"frenet_b"
4
"frenet_m"
4
60
60
11
11
path)
pathNormal)
pathBiNormal)
srfNormal)
srfBiNormal)
frenet_b)
frenet_m)
Parametrisierte Transformation
Dieses Beispiel zeigt eine parametrisierte zusammengesetzte Transformation. Transformiert man
mit dieser eine Kurve, einen Kreisbogen in diesem Fall, so ist diese Kurve von einem zusätzlichen
Parameter abhängig, interpoliert man zwischen der parametrisierten Kurve und einer anderen Kurve, so hängt die Interpolationsfläche ebenfalls von einem Parameter ab. – Man sieht damit, daß die
Konstruktion transparent bleibt in sofern, als Parameter, die in einer Konstruktion auftauchen, seien es nun Zahlen, Vektoren oder ganze Flächen, bis zum Schluß durchgereicht werden können,
um sie in dieser Form in die Werkzeugbibliothek des Anwenders aufzunehmen.
Diese Beispielszene kann man insbesondere auch als Animation auffassen. Die Interpolationsfläche surf hängt von einem reellen Parameter ab. Setzt man dort die Zeit als skalare Größe ein,
so sieht man in der Abbildungen drei Momentaufnahmen einer Animation, in der die Zeit von 0
bis 0.2 läuft.
Zusätzlich wird noch vorgeführt, wie aus der parameterabhängigen Kurve crv_1 eine weitere
Kurve gewonnen werden kann, indem crv_1 an der festen Stelle 0.4 ausgewertet wird und der
Transformationsparameter t zum neuen Kurvenparameter wird. Durch Reparametrisierung benutzt
die neue Kurve crvTrn die Transformation für t 2 [0; 14 ].
crv_0
trn t
= CrvBezier (V3
(V3
(V3
(V3
( 1.0)
( 0.3)
(-0.3)
(-1.0)
(-0.2)
(-0.2)
(-0.2)
(-0.2)
( 1.0))
(-1.0))
(-1.0))
( 1.0))
= TrnCmps (TrnRotYZ t)
(TrnCmps (TrnTranslate (V3 0 0.9 0))
(TrnRotYZ 0.25)
)
crv
= CrvReparam CrvCircle (CrvSegment (V3 1
0 0)
(V3 0.5 0 0))
crv_1 t = CrvTransform
(trn
t) (crv )
surf t = SrfInterpolCrv (crv_1 t) (crv_0)
6.2. HASKELL-BEISPIELE
89
crvTrn
main
++
++
++
++
++
++
++
++
++
++
6.2.3
= CrvReparam (CrvFct (\t -> evalCurve (crv_1 t) 0.4))
(CrvSegment (V3 0 0 0) (V3 0.25 0 0))
= putStr $
(showCurve
"bezier" 20
(crv_0
))
(showCurve
"crvTrn" 20
(crvTrn
))
(showCurve
"kreis0" 20
(crv_1 0.00))
(showCurve
"kreis1" 20
(crv_1 0.05))
(showCurve
"kreis2" 20
(crv_1 0.10))
(showCurve
"kreis3" 20
(crv_1 0.15))
(showCurve
"kreis4" 20
(crv_1 0.20))
(showCurve
"kreis5" 20
(crv_1 0.25))
(showSurface "surf1" 10 20 (surf 0.00))
(showSurface "surf2" 10 20 (surf 0.10))
(showSurface "surf3" 10 20 (surf 0.20))
Funktion als Fläche und patch trimming
Hier wird eine Rechteckfläche deformiert, sodaß zwei der Ränder Parabeln bilden. Diese Fläche
liegt im Standard-2D-Intervall [0; 1] [0; 1] und kann daher in den Parameterbereich einer Kugel
abgebildet werden, die in Polarkoordinaten parametrisiert wird. Dies ergibt ein trimmed patch,
einen Teil einer Freiformfläche. Man sieht auch sehr schön, wo die Parameter-Naht der Kugel
verläuft, wobei die Parameterlinien (0; v) und (1; v) miteinander identifiziert werden. Dies führt
dazu, daß sich die Spitzen der Parabelränder berühren. – Auch hier wurde wieder eine parametrisierte Transformation benutzt, eine Skalierung der verschobenen Kugel, sodaß mehrere Instanzen
ineinander erscheinen.
surf0
= SrfTransform (TrnTranslate (V3 0 (-0.5)
surf1 t = SrfTransform (TrnScale
(V3 1 p 1))
where p=0.5+2.0*r*r
r=t-0.5
surf2
= SrfFct (\u v->evalSurf (surf1 u) u v)
surf3
= SrfTransform (TrnCmps (TrnScale
(V3
(TrnTranslate (V3
surf4 t = SrfTransform (TrnCmps (TrnTranslate (V3
(TrnScale
(V3
surf5 t = SrfFct (\u v-> let p=evalSurf surf3 u v
evalSurf (surf4 t) (x p)
0)) SrfXY
surf0
0.5 1
1))
0.5 0.5 0))) surf2
0 0 2))
t t t))) SrfSphere
in
(y p) )
90
6.2.4
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
Offset einer Fläche in Richtung der Normalen
Eine Kugel kann in sechs gleiche Viereckspatches zerlegt werden, analog zu den Seiten eines
Würfels. In diesem Beispiel wurde die abgebildete bikubische Bézierfläche benutzt, um ein KugelSechstel in Richtung der Normalenvektoren zu verschieben. Die Randkurve des Höhenfeldes liegt
dabei in der xy-Ebene, daher ist es möglich, mehrere Instanzen zu erzeugen, die um neunzig Grad
um die Koordinatenrichtungen gedreht wurden und an den Randkurven aneinander anschließen.
Somit realisiert dieses Beispiel einen stetigen Patchkomplex mit Hollow-Kugelsechstel, allerdings
ohne Zusammenhangsinformationen. – Die sechs Großkreise dienen zur Verdeutlichung der Grenzen zwischen den Sechsteln.
Es ist im übrigen interessant, zu verfolgen, wie der Datenfluß in diesem Programm verläuft.
Der Konstruktor eines Kugelsegmentes steht schließlich nur ein einziges Mal im Code! An dieser
Stelle wird also der Nutzen der referentiellen Transparenz für die Modellierung im besonderen
deutlich; durch die Abwesenheit von Nebeneffekten sind auch die Flächen 4 und 5 wohldefiniert
und müssen nicht befürchten, daß die zugrundeliegende Fläche, das Kugelsechstel, verändert wird,
bevor sie selber ausgegeben sind. Andererseits kann dieses Sechstel auch nicht gelöscht werden,
bevor alle fünf Flächen in der Szene ausgegeben sind, dies sind die Kosten dieses Ansatzes.
Weiter von Bedeutung ist die Tatsache, daß die der Konstruktion zugrundeliegende Fläche an
einer einzigen Stelle definiert wird – und dort geändert werden kann –, beziehungsweise verläuft
die Konstruktion der Fläche 5 aus der Fläche 1 über drei Zwischenkonstruktionen, die ihrerseits
referenzierbar sind. Haskell muß alle drei Zwischenergebnisse aber anlegen, zunächst nicht absehbar ist, ob diese Zwischenergebnisse an anderer Stelle noch benötigt werden, das heißt, die
deformierte Fläche surf_N muß als solche erhalten bleiben, auch wenn in der Folge nur Fläche
5 für den Anwender von Interesse ist. – Ist man aber nur an dem Endresultat einer Konstruktion
interessiert, so bietet es sich an, die Zwischenergebnisse mittels where zu lokalen Variablen zu machen, um dem Compiler eine Chance zur Optimierung zu geben. – Solche Effizienzüberlegungen
sind insbesondere bei größeren Konstruktionen von Bedeutung.
surf_0 = heightfield ( 0.0) ( 0.0) ( 0.0) ( 0.0)
( 0.0) ( 2.2) (-2.2) ( 0.0)
( 0.0) (-2.2) ( 2.2) ( 0.0)
( 0.0) ( 0.0) ( 0.0) ( 0.0)
surf_1 = SrfSphereSegment
surf_2 = SrfTransform (TrnRotXY 0.25) surf_1
surf_N = SrfFct $ \u v -> add (evalSurf surf_1 u v)
(mult (z (evalSurf surf_0 u v))
(normal surf_1 u v))
surf_3 = SrfTransform (TrnRotXY 0.5 ) surf_N
surf_4 = SrfTransform (TrnRotXY 0.25) surf_3
6.2. HASKELL-BEISPIELE
91
surf_5 = SrfTransform (TrnRotZX 0.25) surf_3
liftS srf = SrfTransform (TrnTranslate (V3 0 0 2)) srf
liftC crv = CrvTransform (TrnTranslate (V3 0 0 2)) crv
c0=CrvTransform
c1=CrvTransform
c2=CrvTransform
c3=CrvTransform
c4=CrvTransform
c5=CrvTransform
6.2.5
(TrnRotZX
(TrnRotZX
(TrnRotYZ
(TrnRotYZ
(TrnRotXY
(TrnRotXY
0.125)
(-0.125))
0.25)
0.25)
0.25)
0.25)
CrvCircle
CrvCircle
c0
c1
c0
c1
Parametrisierte Konstruktion: Zaun
Dies ist eine Lösung zu einer Variation der in Kapitel 2.5.1 auf Seite 24 gestellten Aufgabe, einen
Zaun in einer Weise zu modellieren, daß die Lösung änderbar ist und an anderer Stelle wiederverwendet werden kann. Dies wird wieder über eine parametrisierte Konstruktion gelöst, die einzelnen Sprossen des Zaunes werden durch Sampling einer parametrisierten Funktion erzeugt, die zu
t2 [0; 1] eine Sprosse liefert, die die obere und untere Verlaufskurve des Zaunes an dieser Stelle
regelgesteuert miteinander verbinden. Dazu wird die Tangente und die Hauptnormale einer der
Verlaufskurven benutzt, um eine Ellipse im Raum zu definieren. Dann wird eine Kopie dieser Ellipse nach ’unten’ (in Richtung der Binormalen der Ellipse!) verschoben. Das gleiche Werkzeug
wird benutzt, um für die untere Kurve am gleichen Parameterwert eine ähnliche Ellipse zu erzeugen, zusammen mit einer nach ’oben’ verschobenen Kopie. Damit hat man vier Raumkurven.
Aus diesen wird mit dem bezierSweep-Werkzeug eine Fläche definiert, indem – wie bereits beschrieben – für jeden u-Wert die vier Ellipsen ausgewertet werden und die entstehenden Punkte
als Kontrollpolygon einer kubischen Bézierkurve genommen werden. Wertet man diese in v aus,
entsteht eine deformierte zylinderförmige Hülle.
Betrachtet man die Verlaufskurven und den Radius der Sprossen als Konstante, so ist die gesamte Konstruktion eines Zaunpfostens nur von der Stelle auf der Kurve abhängig, somit kann die
Funktion showSurfaces zum Sampling von pfost, mithin zur Produktion von zehn Zaunpfosten
benutzt werden.
zaunBase=CrvBezier
(V3 (-1.0)
(V3 (-0.333)
(V3 ( 0.333)
(V3
1.0
(-0.3)
( 0.3)
( 0.3)
(-0.3)
0)
0)
0)
0)
zaunKopf=CrvBezier
(V3 (-1.0)
(-0.1) 0.5)
(V3 (-0.333) ( 0.1) 0.5)
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
92
(V3 ( 0.333) ( 0.1) 0.5)
(V3
1.0
(-0.1) 0.5)
sprossenRadius=0.03
kreis crv t
= CrvEllipse (evalCurve crv t)
(mult (sprossenRadius) (mainNormal crv t))
(mult (sprossenRadius*0.5) (tangent crv t))
biNormalOffset crv d = CrvFct $
\t -> add (evalCurve crv t) (mult (d) (biNormal crv t))
bezierSweep crv0 crv1 d = SrfFct $
(\u v->evalCurve (CrvBezier (evalCurve crv0 u)
(evalCurve (biNormalOffset crv0 ( d)) u)
(evalCurve (biNormalOffset crv1 (-d)) u)
(evalCurve crv1 u)) v)
bcurv_0 t = kreis zaunBase t
bcurv_1 t = biNormalOffset (kreis zaunBase t) (-0.15)
bcurv_2 t = biNormalOffset (kreis zaunKopf t)
0.15
bcurv_3 t = kreis zaunKopf t
pfosten t = bezierSweep (kreis zaunBase t) (kreis zaunKopf t) 0.15
main = putStr $ (showCurve
"s0"
++ (showCurve
"s2"
++ (showSurfaces "pfost_"
6.2.6
40
40
8 10
10
zaunBase)
zaunKopf)
pfosten )
Parametrisierte Konstruktion: Wendeltreppe
Ergebnis der folgenden Konstruktion ist eine Wendeltreppe, die im Seminarpapier von Jucknath
[Juc95] als Paradebeispiel eines Modells genannt wird, das offensichtlich mit wenigen Parametern
beschrieben ist, und dessen Konstruktion in einem konventionellen Modeller dennoch zum einen
größere Mühe bedeutet, zum anderen empfindlich auf die Änderung der definierenden Parameter
reagiert.
Die Wendeltreppe wird dazu im wesentlichen in vier Teile zerlegt. Das eine sind die Trittflächen, gegeben durch die Funktion stair n, die für ein n eine Stufen-Grundform rotieren läßt. Die
Form dieser Stufe ist in diesem Fall als Eingabeparameter vorgestellt, die undeformierte Grundform ist in der Abbildung exemplarisch als stairExample auf der xy-Ebene dargestellt.
Der zweite Teil der Treppe sind die Frontflächen der Stufen, diese werden durch Interpolation
der Randkurven der eigentlichen Stufen erzeugt, sind also auch als Funktion gegeben, und zwar
stepVert n. Am Anfang und am Ende der Treppe ist zudem je ein kleines Podest angebracht, das
6.2. HASKELL-BEISPIELE
93
obere Podest wird entsprechend der Anzahl der Stufen mitrotiert.
Der Deutlichkeit halber sind die Konstanten, die als Eingabeparameter des Modells dienen
sollen, im Vorspann des Programmtextes angeordnet. – An dieser Stelle sieht man deutlich die
Grenzen der vorliegenden Implementation von Genmod, denn es ist zwar möglich, eine Schar von
Objekten zu erzeugen, aber der offensichtlichte Zusammenhang zwischen den einzelnen Bauteilen
der Wendeltreppe kann nicht ausgedrückt werden. Das bedeutet, es wird hier offensichtlich, daß
weitere integrierende Datenstrukturen wie Patchkomplexe und Szenengraphen notwendig sind,
um Konstruktionen, die noch komplexer sind als die hier vorliegende, strukturieren zu können.
– Die Notwendigkeit von Zusammenhangsinformationen wird zum Beispiel deutlich, wenn man
sich überlegt, die Wendeltreppe noch mit einem Geländer zu versehen, zum Beispiel eine Interpolationsfläche zwischen einem kontinuerlichen Handlauf rechts und links und den Seiten der Trittund der Frontflächen. An dieser Stelle hat es der Modellierer schwer, eine allgemeine Lösung zu
finden, die kein spezielles Wissen über den Aufbau der Wendeltreppe erfordert.
stairDegree
stairHeight
stairRad
stairSteps
stairForm
=
=
=
=
=
1.0/8.0
0.4
0.2
6
heightfieldNrm ( 0.2)
(-0.2)
( 0.1)
(-0.1)
(
(
(
(
0.0)
0.0)
0.0)
0.0)
(
(
(
(
0.0)
0.0)
0.0)
0.0)
( 0.2)
(-0.2)
( 0.1)
(-0.1)
-- *********** spiral staircase object ********************
inner = stairRad
outer = inner+1.0
stepHori = SrfFct $ \u v -> let p=evalSurf stairForm u v
in evalTrans (TrnRotXY ((y p)*stairDegree))
(V3 ((x p)+stairRad) 0 (z p))
stair n
= SrfTransform (TrnCmps (TrnRotXY degree)
(TrnTranslate (V3 0 0 height))) stepHori
where degree = n*stairDegree
height = n*stairHeight
stepVert n = SrfInterpolCrv (CrvKeepV (stair (n-1)) 0.0)
(CrvKeepV (stair (n )) 1.0)
upperFlat
= SrfInterpolCrv (CrvKeepV (stair stairSteps) 0.0)
(CrvSegment p q)
where p = tr (V3 outer ( outer) (stairSteps*stairHeight))
q = tr (V3 inner ( outer) (stairSteps*stairHeight))
tr = evalTrans (TrnRotXY ((stairSteps+1)*stairDegree))
lowerFlat
= SrfInterpolCrv (CrvReverse (CrvKeepV (stair 0) 0.0))
(CrvSegment p q)
where p = tr (V3 inner (-outer) 0.0)
q = tr (V3 outer (-outer) 0.0)
tr = evalTrans (TrnRotXY stairDegree)
94
6.2.7
KAPITEL 6. BEISPIELE UND ERFAHRUNGSBERICHT
rail product und wire product
Als abschließendes Beispiel soll die Definition von wire product und rail product vorgestellt werden. Beide Metashapes sind dem Buch von Snyder entnommen, und so bietet sich eine Möglichkeit
zum direkten Vergleich der verwendeten Sprachen. Die hier gegebene Definition des wire product
läßt sich ohne Schwierigkeiten mit der in 3.1 auf Seite 31 gezeigten Definition in GENMOD identifizieren. Entsprechendes gilt für sämtliche Metashapes, die in GENMOD implementiert sind, mit
Ausnahme derjenigen, die komplexe Operatoren benutzen, wie den Integrationsoperator, das Minimieren unter Nebenbedingungen, constraint solving oder das Lösen von Anfangswertproblemen.
– Kurz gesagt, in bezug auf das elementare Modellieren hat diese Implementation mindestens die
Ausdruckskraft wie GENMOD, verfügt aber nicht über ein Konzept wie Intervallarithmetik und
die damit verbundenen Algorithmen und Datenstrukturen.
In obiger Abbildung ist ein wannenförmiges wire product zu sehen, das über eine planare Verlaufskurve und eine ebenfalls planare Profilkurve in der xy-Ebene definiert wird. Das Profil ist
im Bereich der negativen (grünen) y-Achse zu sehen, daher liegt die wire product-Fläche unterhalb der Verlaufskurve. Zudem sind zwei rail product-Flächen abgebildet, bei denen die Profile,
die entlang der (roten) x-Achse im Bereich von ,1 bis 1 ausgestreckt sind, zwischen zwei Verlaufskurven gespannt werden. Beide Werkzeuge sind typische Variationen der Sweep-Idee und
verkörpern als solche das Prinzip der Generativen Modellierung, weil sie demonstrieren, wie ein
Generator kontinuierlich transformiert wird, um eine Fläche zu konstruieren.
-- Wire Product operator from Snyder, 3.2.1.2, p. 64
data (Curve a, Curve b) => SurfWireProd a b = SrfWireProd a b
instance (Curve a, Curve b) => Surface (SurfWireProd a b) where
evalSurf (SrfWireProd cross wire) u v = V3 (x p) (y p) (y crss_v)
where tang
= tangent
wire u
wire_u = evalCurve wire u
crss_v = evalCurve cross v
left
= V3 (-(y tang)) (x tang) 0.0
p
= add (mult (x crss_v) left) wire_u
-- Rail Product operator from Snyder, 3.2.1.3, p. 66
data (Curve a,Curve b,Curve c)=> SurfRailProd a b c = SrfRailProd a b c
instance (Curve a,Curve b,Curve c)=> Surface (SurfRailProd a b c) where
evalSurf (SrfRailProd crss rail1 rail2) u v = V3 (x p) (y p) (y c_u)
where c_u
= evalCurve crss u
rail1_v = evalCurve rail1 v
rail2_v = evalCurve rail2 v
t
= sub rail2_v rail1_v
m
= mult 0.5 (add rail2_v rail1_v)
p
= add m (mult (0.5*(x c_u)) t)
Kapitel 7
Zusammenfassung
7.1 Modellierung ist funktional objektorientiert
Es wurden viele Belege für die Eingangsthese gefunden, daß die Modellierung inhärent ein funktionaler Prozeß ist. Interaktives Modellieren mit konventionellen Modellern, seien sie approximationsbasiert, rein objektorientiert oder parametrisch, bedeutet die Definition von Objekten in einer
Szene, sowie die Anwendung von Werkzeugen, um die Objekte der Szene sukzessive zu transformieren und zu verknüpfen. Die grundsätzlichen Operationen dabei sind die Objektkonstruktion
und die Abbildung. Beides sind ebenfalls die grundlegenden Operationen in funktionalen Sprachen.
Funktionale Sprachen sind daher grundsätzlich geeignet, um den Prozeß der Modellierung zu
beschreiben. Protokolliert man die Aktionen, die ein Modellierer bei seiner Arbeit ausführt, so
erhält man einen Abhängigkeitsgraphen, wo interaktiv eingegebene Konstanten, Skalare, Punkte,
Kurven und Flächen sowie Gegenstände in Form von Patchkomplexen als Eingaben am Anfang
stehen und sämtliche Werkzeuganwendungen die – implizite oder explizite – Abhängigkeiten zwischen diesen Objekten zu neuen Objekten führen. Veränderung von Objekten findet im eigentlichen Sinne nicht statt, entscheidend ist, daß die Möglichkeit besteht, Teilkonstruktionen an mehr
als einer Stelle in einer Szene zu verwenden. Mehrfachreferenzierung von Objekten bedeutet, daß
ein Abhängigkeitsbaum zu einem DAG wird.
Es wurde eine C++-Implementation von Abbildungsobjekten vorgestellt, die eine Auswertung
eines Abbildungsgraphen effizient unterstützt und der Art der verwendeten Datenstrukturen keine
Beschränkungen auferlegt. Jede extern definierte C++-Klasse, die für die Modellierung wichtig
erscheint, kann mit Hilfe des Template-Mechanismus als Variablenobjekt in das Modelliersystem
aufgenommen werden. Es lassen sich Abbildungen beliebiger – auch variabler – Signatur konstruieren, die eine Liste von Objekten auf ein neues Objekt abbilden. Insbesondere sind solche
Memberfunktionen von Variablenklassen zur Definition von Abbildungen geeignet, deren Parameter ebenfalls in das System in Form von Variablenklassen integriert sind.
Die einzige Einschränkung für Abbildungen ist, daß sie die Eingangsparameter nicht verändern, sondern sich nur auf das Erzeugen eines neuen Objektes beschränken. Dies ist aber konsistent mit dem Vorgehen in einem interaktiven Modeller, wo Objekte mit Hilfe von Handles zwar
scheinbar verändert werden, in Wirklichkeit handelt es sich dabei aber nur darum, die richtigen
skalaren Parameter für eine Form zu finden. Ist ein gültiges Objekt erzeugt, wird es nicht weiter
interaktiv bearbeitet und in der definierten Form gerendert.
Das Verbot destruktiver Updates einerseits ist andererseits gleichbedeutend mit der Abwesenheit von Seiteneffekten. Für die Modellierung bedeutet das, daß sich der Modellierer darauf
verlassen kann, daß sich ein referenziertes Objekt nicht plötzlich verändert.
95
96
KAPITEL 7. ZUSAMMENFASSUNG
Bedeutet geometrische Modellierung die Anwendung von Abbildungen auf Objekte, so bedeutet funktionale Programmierung das Rechnen mit Abbildungen. Im funktionalen Programmierparadigma finden sich daher viele Konzepte zum Umgang mit der Beschränkung, die die
Abwesenheit von Seiteneffekten bedeutet. Sei es, daß statt Schleifen Rekursion verwendet werden, sei es, daß Funktionen ’first class citizens’ sind. Gleichzeitig zeigt das Beispiel von Haskell,
daß das nicht bedeutet, die Vorteile eines statischen Typsystems und der objektorientierten Programmierung aufzugeben. Es ist nur notwendig, den Signaturbegriff und den Begriff des Objektes
in anderer – letztendlich auch der Modellierung angepaßter – Weise zu fassen.
Durch die Verwendung funktionaler Prinzipien als Grundlage für die Beschreibung generativ
modellierter Objekte wird daher das Optimum an Ausdrucksfähigkeit erreicht: Generative Modelle
lassen sich in der kürzesten und verständlichsten Weise beschreiben.
7.2 Modellieren mit Abbildungen ist effizient
Neben den theoretischen Überlegungen hat die Generative Modellierung ganz klar eine praktische
Relevanz. Modellierung bedeutet nicht mehr, Szenen nach dem ’trial and error’-Verfahren zu konstruieren, sondern der Modellierer hat die Möglichkeit, den inneren Aufbau eines Modells zu explizieren. Präzises Wissen über den inneren Aufbau modellierter Objekte kann in Form anwenderdefinierter Werkzeuge zum Aufbau einer Bibliothek von Modelliervorschriften verwendet werden,
es ist nicht mehr allein Programmierern von ’Plugins’ vorbehalten, Modeller um Makro-Objekte
zu erweitern. Es kann unterschieden werden zwischen den speziellen Skalaren und Kontrollpunkten, die zu einer Objektinstanz gehören, und der abstrakten Verknüpfung dieser Eingabewerte
zu einem Modell. Die Verwendung höherer Funktionen gestattet es zusätzlich, die verwendeten
Werkzeuge selber zu Parametern des Modells zu machen, also auf noch höherem, abstrakteren
und damit mächtigerem Niveau zu modellieren: Zur Beschreibung wissenschaftlicher Visualisierungen beispielsweise muß man Zugriff auf die vielen unterschiedlichen Darstellungsmittel der
Computergrafik haben, die eingangs in Kapitel zwei in Abschnitt 2.2 geschildert wurden.
Viele der benutzten Konzepte finden darüberhinaus einen ganz natürlichen Ausdruck in der
funktionalen Beschreibung. Aufruf eines Objektkonstruktors und interaktive Eingabe von Parametern bedeutet einfach die Definition einer Variablen. Anwendung einer beliebigen Transformation
oder eines Werkzeugs bedeutet einen Funktionsaufruf. Eine Szenehierarchie entsteht ganz natürlich aus den Transformationen im Abhängigkeitsgraphen, insbesondere kann man die Konstruktion von Objekten grundsätzlich im LKS vornehmen. Referenzobjekte entstehen durch Anwendung
einer Funktion auf eine bereits existierende Variable, es ergibt sich eine neue Variable. Alle Größen einer Szene sind parametrisierbar, insbesondere also die verwendeten Zahlen, eine Animation
erhält man wie in Beispiel 6.2.2 indem man die Zeit als skalaren Parameter verwendet, der an
jeder gewünschten Stelle Einfluß nehmen darf. Materialien kann man in der allgemeinsten Form
durch Angabe des Reflexionsverhaltens mit einer ’bidirectional reflection distribution function’,
einer BRDF, ausdrücken, das Färben eines Flächenteils geschieht durch Auswertung der BRDF
für den dortigen Ein- und Ausfallwinkel des Lichts. Eine Lichtquelle ist eine Abbildung, die jedem
Raumpunkt eine Lichtintensität zuordnet. Eine Kamera ist eine umkehrbare Abbildung zwischen
einem Punkt einer Projektionsfläche und einem Strahl im Raum. Schatten werden appoximativ
berechnet, indem eine Szene und eine Reihe von Lichtquellen auf eine beleuchtete Approximation
der Szene abgebildet werden. Ein Renderingverfahren ist letztendlich nichts als eine Abbildung
einer beleuchteten Szene auf ein Bild, eine Abbildung einer Liste von Szenen ergibt eine Bildsequenz, eine Animation. – Die Realisierung von Freiformkurven und -Flächen, einzelnen Patches,
und von vielen Werkzeugen wurde bereits detailliert vorgeführt. Dabei wurde im Rahmen des cns-
7.3. MÖGLICHE REALISIERUNGEN
97
Paketes vor allem beschrieben, wie eine Klassenhierarchie für parametrische Freiformkurven und
-Flächen aussehen kann, und wie numerisch approximierte Ableitungen eingesetzt werden.
Partikelsysteme kann man als Menge von Trajektorien beschreiben, ist etwa ein kontinuierliches Kraftfeld aus überlagerten Einzelkräften vorgegeben, so sind die Trajektorienkurven Ergebnis
eines Anfangswertproblems, also abhängig von Startpunkt und Startgeschwindigkeit eines Partikels. Ein bestimmtes Partikelsystem kann als stochastisches Sampling der Trajektorienfunktion
gesehen werden, eine Animation ergibt sich durch regelmäßiges Sampling der Trajektorien. Interagierende Partikel erhält man durch Angabe einer Liste von Verbindungen, ein neuer Zustand entsteht durch Abbildung eines alten Zustands durch eine Übergangsfunktion. Den gleichen Ansatz
einer Zustands-Übergangsfunktion kann man für physikbasiertes Modellieren verwenden, dies ist
auch das Verfahren für die Ein/Ausgabe in funktionalen Sprachen.
Der große Vorteil einer vereinheitlichten Beschreibung liegt auf der Hand: Auf alle Aspekte
eines Modellierprozesses kann mit demselben Mechanismus zugegriffen werden. Eine wissenschaftliche Visualisierung erfolgt, indem Dateien mit Rechenergebnissen regelgesteuert zu Szenen und Animationen verarbeitet werden. Eine einzelne Beschreibungsdatei, ein geometrisches
Programm, wird von einem Renderer abgearbeitet, so entsteht eine komplette Animation alleine aus der Verknüfung von Eingabedaten und Abbildungsregeln, wenn die Beschreibungssprache
für diese Regeln mächtig genug ist. Modellierung im Großen erfordert daher eine geometrische
Programmiersprache, und die Beispiele in Kapitel sechs zeigen, wie dies realisiert werden kann.
7.3 Mögliche Realisierungen
Generative Modellierung geschieht auf einem höheren Abstraktionsgrad. Dies beinhaltet einerseits
den Abschied vom gewohnten Herumstochern in der zweidimensionalen Projektion einer dreidimensionalen Szene, andererseits zwingt es in der einen oder anderen Form zum Programmieren.
Programmsysteme wie Geomview werden ferngesteuert, indem man ihnen einen Strom von
low-level-Kommandos schickt. Einen objektorientierten Modeller kann man sich so vorstellen,
daß er eine Schnittstelle zwischen dem Anwender und einer solchen ’geometry engine’ definiert,
also Benutzerinteraktion in Kommandos für den Aufbau einer Geometrie umwandelt. Auf diese Weise trennt man das schwer formalisierbare Benutzerinterface von dem zugrundeliegenden
Sprachkonzept. Ändert man die Kommandosprache und benutzt eine Beschreibung von Modellen
auf einem höheren Niveau, so kann man beispielsweise ein GENMOD- oder Haskell-gesteuertes
Geometriemodul konzipieren. Snyders Modellieransatz kann also als eine Trennung an einer Stelle verstanden werden, die einen Vergleich von Modellern ermöglicht, eine Beschreibungssprache,
die die gesamten Benutzermanipulationen in eine “lebendige”, das heißt tatsächlich ausführbare
Szenenbeschreibung umwandelt.
Die Rolle eines skriptgesteuerten Geometriemoduls fällt im vorliegenden Fall dem MRT zu.
Es stellt sich also das Problem, zum einen eine generative Programmiersprache zu definieren, zum
anderen den MRT auf die Verarbeitung generativer Modelle vorzubereiten. Antworten auf beide
Fragen wurden in dieser Arbeit vorgeschlagen.
Die C++-Implementation von Genmod ermöglicht es, die zusammengesetzten Abbildungen
zur Laufzeit zu konstruieren, die bei Aufruf eines Metashapes wie m_wire zurückgegeben werden. Überdies lassen sich für sämtliche Kurven und Flächen, die in den vorangegangenen HaskellBeispielen per show ausgegeben wurden, zusammengesetzte C++-Genmod-Abbildungen angeben, die genau diese Objekte repräsentieren. Das bedeutet, die innere Organisation des MRT ist
auf generative Modelle vorbereitet.
Zum anderen ist es offensichtlich nicht nötig, eine funktionale Sprache als geometrische Pro-
KAPITEL 7. ZUSAMMENFASSUNG
98
grammiersprache zu verwenden. Ein wesentliches Ergebnis dieser Arbeit ist zwar die Erkenntnis,
daß funktionale Sprachen dem Wesen der Modellierung am besten angepaßt sind, doch Beispiele
wie TBag oder GENMOD zeigen, daß auch in normalerweise prozedural orientierten Sprachen
ein funktionaler Programmierstil möglich ist. Im Prinzip läßt sich jede C-erweiterbare, interpretierte, bereits verbreitete Kommandosprache wie Python [Lut96], Tcl [Ous95], Perl [WS91], oder
auch eine Minimalimplementation eines C-Interpreters wie Slang [Dav96] verwenden, um ihr die
generative Struktur “unterzuschieben”. Das wire product könnte man sich in einer ersten PythonImplementation zum Beispiel folgendermaßen vorstellen:
def m_wire(cross, wire, operation):
that = operation(wire,m_real(0))
t
= m_normalize(that)
n
= m_vector(m_minus(m_y(t)),m_x(t),m_real(0))
p
= m_add(m_mult(m_x(cross),n)
q
= m_add(p,vector(m_real(0),m_real(0),m_y(cross)
return q
mywire = m_wire(mycross, mywire, m_derivative)
Python hat den Vorteil, auch das Überladen von Operatoren zuzulassen, wodurch der Code
entsprechend knapper und lesbarer würde. Entscheidend ist jedoch, daß hier ein Abbildungsgraph
definiert wird, die Funktionsaufrufe würden in einer solchen Implementation dazu dienen, darunterliegende Genmod-Abbildungen zusammenzusetzen, wo die Typüberprüfung und das Binden in
der gewohnten Weise stattfinden kann. Python gestattet als Interpreter überdies die Übergabe von
Funktionen wie m_derivative in obigem Beispiel, die Typüberprüfung findet erst bei Ausführung
statt. Nichtsdestotrotz ist mywire als Resultat einer Python-Auswertung dieses Programmteils wie
gehabt ein MRT-Objekt, das gerendert werden kann.
Haskell-Genmod ist langsam. Die Auswertung der Beispiele, das Sampling von Kurven und
Flächen, zeichnet sich nicht durch eine hohe Ausführungsgeschwindigkeit aus, obwohl nicht interpretierter, sondern compilierter Code verwendet wurde. Dies hängt damit zusammen, daß Haskell als allgemeine Programmiersprache konzipiert wurde. Erweitert man dagegen eine interpretierte Standardsprache durch Wrapper so, daß das Resultat einer Ausführung C++-GenmodAbbildungen sind, ist die Ausführungsgeschwindigkeit mit hoher Wahrscheinlichkeit höher als
bei Verwendung einer funktionalen Standardsprache oder bei reiner Implementation in einer interpretierten Sprache. – Eine weitere Möglichkeit ist die Verbindung von interpretiertem und vorkompiliertem Code, wie er von Haskell-Compilern wie HBC der Chalmers University of Technology
erlaubt wird; die meisten Haskell-Compiler haben zudem eine Schnittstelle zu C, bzw. sind selber
in C geschrieben. – Ein ideales Konzept zur Realisierung eines generativen Modellers wäre daher,
zum einen den Aufbau einer Werkzeugbibliothek durch eine leicht zu erlernende Skriptsprache
zu erleichtern, andererseits Standard-Handles zur Parameterauswahl zur Verfügung zu stellen, um
für eigene Werkzeuge Gizmos zu schreiben, die Kontrollpunkte und Skalare als Parameter für aufgerufene Werkzeuge liefern. Denn benutzt man keine höheren Funktionen, kommen zur Auswahl
von Parametern nur Zahlen, Punkte, Kurven und Patches in Frage. Dann genügt es – auch für
die Mehrzahl der Haskell-Beispiele –, die folgenden interaktiven Operationen zu unterstützen und
“mitzuschreiben”:
Werkzeuganwendung bzw. Konstruktion eines Objektes
Auswahl eines Objektes in der Szene als Werkzeugparameter oder
Leerlassen eines Parameters, dieser wird Parameter des Modells
Funktionsdefinition: Zusammengesetzte Abbildung als neues Werkzeug
7.4. AUSBLICK
99
In der Grundform der Generativen Modellierung werden also – wie in GENMOD – syntaktisch gesehen nur ganz wenige Sprachkonstrukte benutzt, die kein echtes Programmieren erfordern
und interaktiv ausgelöst werden können. Entscheidend ist jedoch, daß ein sauberes, durchdachtes
Sprachkonzept im Hintergrund steht, dessen Ausdrucksfähigkeit man formal untersuchen kann;
mit zunehmender Erfahrung auf dem Gebiet der “interaktiven Programmierung” wird sich herauskristallisieren, wie weit man diesen Ansatz ausbauen kann.
Klar ist jedoch, daß ab einem bestimmten Abstraktionsgrad die textuelle Eingabe, wirkliches Programmieren, wesentlich einfacher und transparenter ist. Variablen erhalten sprechende
Bezeichner, man sieht auf einen Blick, welche Variable wo referenziert wird, und der innere Aufbau, der Datenfluß innerhalb eines Modells läßt sich bei disziplinierter Programmierung oftmals
alleine durch das Programmlayout auf einen Blick erfassen.
Durch einen hybriden Modellieransatz läßt sich schließlich möglicherweise ein fließender,
schmerzfreier Übergang zwischen beiden Welten erreichen.
7.4 Ausblick
Rein geometrisch ist der entscheidende Vorteil der Generativen gegenüber der konventionellen
Modellierung die Tatsache, daß ein Modell schließlich nicht approximativ vorliegt, sondern als
Abbildung, die jederzeit an jeder Stelle ausgewertet werden kann.
Um vom Modellieren mit BReps aber gänzlich Abschied nehmen zu können, ist es entscheidend, die aufwands- und platzmäßig harten Probleme zu lösen, die beim Lösen impliziter Gleichungssystemen auftreten, um schließlich CSG auch auf Freiformflächen zu realisieren, Strahlschnitte effizient zu berechnen und Isoflächen extrahieren zu können. Snyder hat viele dieser Probleme mit Hilfe von Inklusionsfunktionen in den Griff bekommen, was nur möglich war, weil in
Generativen Modellen das Konzept der Operatormethode konsequent realisiert werden kann.
Berechnung der Inklusionsfunktion führt jedoch zwangsläufig zur Benutzung von Methoden
der Intervallrechnung. Diese ist in den vorliegenden Implementationen von Genmod noch in keiner Weise realisiert, die verschiedenen Ideen, die Snyder in seinem Buch vorlegt, lassen aber
vermuten, daß sich dort ein ganz eigenes Arbeitsgebiet ergeben wird. Intervallrechnung führt zu
Methoden wie ’merging’ von kd-Bäumen und linearer Programmierung, die auf den ersten Blick
nicht viel mit kontinuierlichen Abbildungen zu tun haben.
Das zweite wichtige Problem, das sich aus dieser Arbeit ergibt, ist die Frage der Realisierung
von Patchkomplexen. Interessant hierbei ist vor allem die Frage, wie kontinuierliche Strukturen wie Freiformflächen und -kurven mit der diskreten Struktur eines eingebetteten Graphen in
Einklang gebracht werden können. Es ist klar, daß sich die Oberfläche zusammengesetzter, geschlossener Gegenstände als Ansammlung von Flächenstücken auffassen läßt, die entlang von
Raumkurven verklebt werden. Alles andere als klar ist jedoch, wie sich der Konstruktionsprozeß
eines solchen Gegenstands beschreiben läßt.
Zum einen liegt dies in der Vielfalt der Verfahren begründet. Coons- oder Hermite-Patches
gibt es in drei-, vier- und n-eckiger Form, diese lassen beispielsweise zu, daß man einfach ein
hinreichend dichtes, irreguläres Kurvennetz vorgibt, zwischen das Flächen gespannt werden, ohne
daß es zu Knicken kommt. Bei diesem Ansatz ergeben sich jedoch Probleme wie die Spezifikation von Twist-Vektoren, um ’flat spots’ zu vermeiden [Far90], und die praktisch begrenzte
Ordnung der Stetigkeit entlang der Ränder. Eine andere Möglichkeit ist das Vorgeben einzelner
Flächenstücke, die dann verblendet werden, um die Lücken auszufüllen. Hierbei aber ergeben sich
zumeist zusätzliche Freiheitsgrade, die eine sehr unanschauliche geometrische Bedeutung haben
und für die ein sinnvoller Wert schwer heuristisch abzuschätzen ist. Die Isolierung der Freiheits-
100
KAPITEL 7. ZUSAMMENFASSUNG
grade selber ist ebenfalls schwierig, wenn man die Bedingung der Stetigkeit auf die Stetigkeit der
Form reduziert, also Geometrische Stetigkeit vorschreibt, die unabhängig ist von der Parametrisierung der einzelnen Flächen. Und schließlich möchte man natürlich in der Lage sein, heterogene
Patchkomplexe aus all den verschiedenen Typen von Flächenelementen zu bauen, die das Füllhorn der Geometrischen Modellierung bereithält. Dabei kommt es zu dem Problem, einheitliche
Übergangsbedingungen für die verschiedensten Flächentypen zu formulieren.
Ein weiteres Problem ist es, rein syntaktisch eine Möglichkeit zum bequemen Umgang mit
Graphstrukturen zu finden. Wie eingangs erläutert, sind BReps letztendlich auch nichts anderes
als Patchkomplexe. BReps sind die dominierende Datenstruktur in der geometrischen Modellierung, entsprechend gibt es eine uferlose Menge von Implementationen und Dateiformaten, alleine
in dieser Arbeit wurde eine Reihe von ihnen erwähnt. Die Verwendung von BReps und ihr Aufbauen innerhalb eines Programms hängt aber vom API der Implementation ab und ist meist extrem
kompliziert, weil es das blinde Navigieren in einem Graphen und das Einhalten bestimmter Konsistenzbedingungen erfordert. Die meisten 3D-APIs ermöglichen daher einfach das Einlesen von
Geometrien aus Dateien und unterstützen dazu die gängigsten Dateiformate. Dieser Ausweg besteht natürlich nicht, wenn bei der Modellierung von Patchkomplexen die Graphstruktur erst nach
und nach entsteht und die Graphen wegen der Mächtigkeit der Formen in der Regel viel kleiner
sind als BReps. Um Teile eines Patchkomplexes tatsächlich handhaben zu können, ist im Rahmen
der Entwicklung des cns-Paketes eine weitere Implementation in Form einer Klasse für eingebettete Graphen entstanden. Dazu wurde die ’winged-edge’-Datenstruktur gekapselt und der Zugriff
auf die in [Män88] beschriebenen, algebraisch fundierten Euleroperatoren beschränkt, die zu einer
übersichtlichen Zahl – im wesentlichen acht – von Memberfunktionen des Graphenklasse geführt
haben. Schwerpunkt hierbei war es, einen zugleich bequemen wie auch sicheren Zugang zur Datenstruktur durch Iteratoren [SL95] zu ermöglichen, die im topologischen Sinne eine Stelle des
Graphen repräsentieren, also Zeiger auf eine Fläche, eine Kante der Fläche und einen Knoten der
Kante enthalten. Damit diese Datenstruktur zukünftig als Basis für Patchkomplexe dienen kann,
war es aber nötig, sie in Form eines C++-Templates zu realisieren, damit die unterschiedlichsten
Flächen und Kurven zusammen mit den nötigen Verwaltungsinformationen wie Parameterwechsel
entlang der Ränder und (u; v)-Werte von Ecken in einer gemeinsamen Struktur gehalten werden
können. – Dennoch ist im Hinblick auf eine komfortable Bedienbarkeit an dieser Stelle noch einige Arbeit zu leisten, vor allem wenn man an die Verbindung von Patchkomplexen und Generativer
Modellierung unter dem Dach einer geometrischen Programmiersprache denkt.
Zuletzt stellt die Verbindung der kontinuierlichen Objekte und der diskreten Struktur selber
auch noch eine Schwierigkeit dar. Patchkomplexe haben nicht nur die topologische Konsistenz des
Graphen zu respektieren, sondern es müssen ebenfalls geometrische Bedingungen erfüllt werden.
Es ist aber vollkommen unklar, wie etwa sichergestellt werden soll, daß topologisch benachbarte
Flächen auch geometrisch aneinandergrenzen, daß zwischen ihnen kein Spalt bleibt, die breiter als
ein Epsilon ist, und wie Randkurven definiert werden können, die tatsächlich in beiden Flächen
enthalten sind. Zudem ist vorstellbar, daß es während des Aufbauprozesses einem Patchkomplex
durchaus erlaubt sein soll, mehrere Hollows aneinandergrenzen zu lassen, unzusammenhängende Flächen oder Kurven zu enthalten, die später noch aneinandergefügt werden, oder alleinstehende, unverbundene Ecken im Raum als Anhaltspunkte für eine Konstruktion zu benutzen. Für
BReps wurde mit den Euleroperatoren eine schöne Methode gefunden, die Datenstruktur zu jedem
Zeitpunkt konsistent zu halten und keine hängenden oder NULL-Zeiger zulassen zu müssen. Für
Patchkomplexe steht ein ähnlich eleganter Zugang noch aus.
Der letzte Problemkomplex in diesem Zusammenhang ist die Definition geeigneter Methoden auf Patchkomplexen. Der Strahlschnitt zum einen zeigt an Kanten und Schnittstellen zwischen zwei oder, schlimmer noch, mehreren Patches zumeist ein pathologisches Verhalten, weil
7.4. AUSBLICK
101
Strahlen von keiner oder von mehreren Flächen in verschiedene Richtungen reflektiert werden.
Dadurch, daß man aber Informationen über den lokalen Zusammenhang besitzt, hat man zumindest eine Chance, dieses Problem in den Griff zu bekommen. Dennoch stellt sich das Problem,
effizient herauszufinden, daß ein Strahl Gefahr läuft, in der Nähe eines oder mehrerer Flächenränder aufzutreffen. – Die andere zu implementierende Standardmethode ist die Umwandlung in
ein BRep, hier müssen geeignete Methoden gefunden werden, um unterschiedlich parametrisierte Patches reißverschlußartig zu verbinden. Zudem eignet sich ein reguläres Sampling überhaupt
nicht, um ein gemeinsames BRep für aneinandergrenzende Patches zu bauen, deren geometrische
Abmessungen stark unterschiedlich sind. Hier müssen also Strategien für adaptives Sampling im
Bildraum gefunden werden. Schwierigkeiten stellen sich bei heterogenen Patchkomplexen aber
bereits bei grundsätzlich trivialen Operationen wie der affinen Transformation; bei generativen
Modellen könnte man alle Abbildungen, die parametrische Patches definieren, mit einer Transformationsabbildung komponieren.
Alles in allem tun sich also viele interessante Fragestellungen auf, wenn man bestrebt ist, das
Problem einer geometrischen Programmiersprache für heterogene Patchkomplexe in Verbindung
mit Generativer Modellierung und der Lösung von Systemen impliziter Gleichungen anzugehen.
KAPITEL 7. ZUSAMMENFASSUNG
102
Literaturverzeichnis
[Aut96a]
Autodesk Inc. 3D Studio Max Tutorials, 1996.
[Aut96b]
Autodesk Inc. 3D Studio Max User’s Guide, 1996. Part1, Part 2.
[Aut96c]
Autodesk Press. Benutzerhandbuch AutoCAD R12, 1996.
[BBB87]
Richard H. Bartels, John C. Beatty und Brian A. Barsky. An Introduction to Splines for Use in
Computer Graphics & Geometric Modeling. Morgan Kaufmann Publishers, California, 1987.
[Ben97]
Heinzgerd Bendels. Eine topologische Datenstruktur und ihre Anwendungen im 3DGraphiksystem MRT. Diplomarbeit, Universität Bonn, 1997.
[BFH95]
Heinzgerd Bendels, Dieter W. Fellner und Sven Havemann. Modellierung der Grundlagen. In
D. W. Fellner, Hrsg., Beiträge zum internationalen Workshop on Modeling – Virtual worlds –
Distributed graphics., 1995.
[CLR90]
Thomas H. Cormen, Charles E. Leiserson und Ronald L. Rivest. Introduction to Algorithms.
MIT Press, 1990.
[CS96]
Sven Campagna und Philipp Slusallek. Improving Bezier Clipping and Chebychev Boxing
for Ray Tracing Parametric Surfaces. In B. Girod, H. Niemann und H.-P. Seidel, Hrsg., 3D
Image Analysis and Synthesis ’96, Seiten 95–102. infix, 1996.
[Dav96]
John E. Davis. S-Lang Programmer’s Guide, 0.1. Auflage, 1996. Softwaredokumentation.
[do 93]
Manfredo P. do Carmo. Differentialgeometrie von Kurven und Flächen, Kapitel 2. Vieweg,
1993.
[Ell96]
Conal Elliott. A Brief Introduction to ActiveVRML. Technical Report MSR-TR-96-05, Microsoft Research, 1996. http://research.microsoft.com/pubs/tr-96-05a.html.
[ESYAE95] Conal Elliott, Greg Schechter, Ricky Yeung und Salim Abi-Ezzi. TBAG: A High Level Framework for Interactive, Animated 3D Graphics Applications. In Proc. SIGGRAPH ’94, Seiten
421–434. ACM, 1995.
[Far90]
Gerald Farin. Curves and Surfaces For Computer Aided Geometric Design. Academic Press,
San Diego, CA, 2. Auflage, 1990.
[Fel92]
W. D. Fellner. Computergrafik. BI-Wissenschaftsverlag, Zürich, 2.. Auflage, 1992.
[Fel96]
Dieter W. Fellner. MRT – A Teaching and Research Platform for 3D Image Synthesis. IEEE
CG&A, 16(3), Mai 1996.
[FG94]
Joanne C. Fullagar und Open Inventor Achitecture Group. Open Inventor C++ Reference
Manual. Silicon Graphics Inc., Addison-Wesley, 1994.
[Fis95a]
Martin Fischer. Reference Pointers for Class Hierarchies. Technical Report IAI-TR-95-12,
Universität Bonn, August 1995.
[Fis95b]
Martin Fischer. Runtime Type Information for Class Hierarchies. Technical Report IAI-TR95-11, Universität Bonn, Juli 1995.
[HJe92]
P. Hudak, S. Peyton Jones und P. Wadler (editors). Report on the Programming Language
Haskell (Version 1.2). ACM SIGPLAN Notices, 27(5), 1992.
[HL92]
Josef Hoschek und Dieter Lasser. Grundlagen der geometrischen Datenverarbeitung. Teubner, Stuttgart, 2. Auflage, 1992.
[Hud89]
Paul Hudak. Conception, Evolution, and Application of Functional Programming Languages.
ACM Computing Surveys, 21(3), 1989.
[Juc95]
Oliver Jucknath. Programmiersprache zur Beschreibung von 3D-Objekten. Seminarpapier,
1995.
LITERATURVERZEICHNIS
103
[Lut96]
Mark Lutz. Programming Python. Nutshell Handbook. O’Reilly & Associates, Inc., 1996.
[Män88]
Martti Mäntylä. An Introduction to Solid Modeling. Computer Science Press, Rockville,
1988.
[Ous95]
John K. Ousterhout. Tcl und Tk. Addison-Wesley, 1995.
[Prö92]
Prömel. Diskrete Mathematik III. Vorlesung, Wintersemester 1991/1992.
[SDK96]
Schmitt, Deussen und Kreeb. Einführung in graphisch-geometrische Algorithmen. B. G.
Teubner, Stuttgart, 1996.
[SL95]
Alexander Stepanov und Meng Lee. The Standard Template Library. Hewlett-Packard Laboratories, 1995.
[Sny92]
John M. Snyder. Generative Modeling for Computer Graphics and CAD. Academic Press,
1992.
[WG94]
Josie Wernecke und Open Inventor Achitecture Group. The Inventor Mentor. Silicon Graphics Inc., Addison-Wesley, 1994.
[WS91]
Larry Wall und Randal L. Schwartz. Programming Perl. Nutshell Handbook. O’Reilly &
Associates, Inc., 1991.
[WW92]
Alan Watt und Mark Watt. Advanced Animation and Rendering Techniques. Addison-Wesley,
1992.
104
KAPITEL 7. ZUSAMMENFASSUNG
Anhang A
Haskell-Code
Die folgenden beiden Abschnitte enthalten die komplette Definition von Genmod in Haskell in
zwei Dateien von 15852 beziehungsweise 1453 Bytes.
A.1
Die Datei Genmod.hs
module Genmod(
Curve
(derivOne,derivTwo,tangent,mainNormal,biNormal,
evalCurve,diff_1,diff_2),
Surface
(tangentU,tangentV,normal,evalSurf),
Transform (evalTrans),
CurveX
(CrvX),
CurveSegment
(CrvSegment),
CurveTransform
(CrvTransform),
CurveFct
(CrvFct),
CurveInSurf
(CrvInSurf),
CurveReparam
(CrvReparam),
CurveReverse
(CrvReverse),
CurveInterpol
(CrvInterpol),
CurveKeepU
(CrvKeepU),
CurveKeepV
(CrvKeepV),
CurveBezier
(CrvBezier),
CurveCircle
(CrvCircle),
CurveEllipse
(CrvEllipse),
SurfXY
(SrfXY),
SurfTransform
(SrfTransform),
SurfFct
(SrfFct),
SurfParamCrv
(SrfParamCrv),
SurfBezier
(SrfBezier),
SurfSphere
(SrfSphere),
SurfSphereSegment
(SrfSphereSegment),
SurfInterpolCrv
(SrfInterpolCrv),
SurfCoons
(SrfCoons),
SurfWireProd
(SrfWireProd),
SurfRailProd
(SrfRailProd),
TransformCmps
(TrnCmps),
TransformFct
(TrnFct),
TransformTranslate (TrnTranslate),
TransformScale
(TrnScale),
TransformRotXY
(TrnRotXY),
TransformRotYZ
(TrnRotYZ),
TransformRotZX
(TrnRotZX),
105
ANHANG A. HASKELL-CODE
106
line,
heightfield,
heightfieldNrm,
sampleCurve,
sampleSurf,
showSurface,
showCurve,
showSurfaces,
showCurves) where
import Vectors
-- *****************************************
-- ** HASKELL-GENMOD KERNEL
-- *****************************************
class Curve c
derivOne
derivTwo
tangent
mainNormal
biNormal
evalCurve
diff_1
diff_2
where
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
:: c ->
Float
Float
Float
Float
Float
Float
Float
Float
->
->
->
->
->
->
->
->
Vector3
Vector3
Vector3
Vector3
Vector3
Vector3
Vector3
Vector3
diff_1
crv t = sub (evalCurve crv (t+diffQuot_h))
(evalCurve crv (t-diffQuot_h))
diff_2
crv t = sub (add (evalCurve crv (t+diffQuot_h))
(evalCurve crv (t-diffQuot_h)))
(add p p)
where p=evalCurve crv t
derivOne
crv t = mult diffQuot_2h (diff_1 crv t)
derivTwo
crv t = mult diffQuot_h2 (diff_2 crv t)
tangent
crv t = normalize (diff_1 crv t)
biNormal
crv t = normalize (cross (diff_1 crv t) (diff_2 crv t))
mainNormal crv t = normalize (cross (cross d1 d2) d1)
where d1 = diff_1 crv t
d2 = diff_2 crv t
class Surface
tangentU
tangentV
normal
evalSurf
tangentU
tangentV
normal
s where
:: s ->
:: s ->
:: s ->
:: s ->
Float
Float
Float
Float
->
->
->
->
Float
Float
Float
Float
->
->
->
->
Vector3
Vector3
Vector3
Vector3
srf u v = tangent (CrvKeepV srf v) u
srf u v = tangent (CrvKeepU srf u) v
srf u v = normalize $ cross (tangentU srf u v)
(tangentV srf u v)
class Transform t where
evalTrans :: t -> Vector3 -> Vector3
A.1. DIE DATEI GENMOD.HS
-- *****************************************
-- ** CURVES
-- *****************************************
-- Identical x curve
data CurveX = CrvX
instance Curve CurveX where
evalCurve (CrvX) t = V3 t 0.0 0.0
-- Make a line segment from two points
data CurveSegment = CrvSegment Vector3 Vector3
instance Curve CurveSegment where
evalCurve (CrvSegment p0 p1) t = line p0 p1 t
-- Make a transformed curve a curve
data (Transform t, Curve c) => CurveTransform t c = CrvTransform t c
instance (Transform t, Curve c) => Curve (CurveTransform t c) where
evalCurve (CrvTransform trans crv) t = evalTrans trans (evalCurve crv t)
-- Make a function a curve
data CurveFct = CrvFct (Float->Vector3)
instance Curve CurveFct where
evalCurve (CrvFct f) t = f t
-- Make a curve in a surface’s parameter space a space curve
data (Surface s, Curve c) => CurveInSurf s c = CrvInSurf s c
instance (Surface s, Curve c) => Curve (CurveInSurf s c) where
evalCurve (CrvInSurf srf crv) t = evalSurf srf (x p) (y p)
where p = evalCurve crv t
-- Reparameterization: evaluate a curve at t and take the x coordinate
-- of the computed point to be the parameter for a second curve
data (Curve c, Curve d) => CurveReparam c d = CrvReparam c d
instance (Curve c, Curve d) => Curve (CurveReparam c d) where
evalCurve (CrvReparam f g) t =
evalCurve f (x (evalCurve g t))
-- Reversal of a curve
data (Curve c) => CurveReverse c = CrvReverse c
instance (Curve c) => Curve (CurveReverse c) where
evalCurve (CrvReverse crv) t = evalCurve crv (1.0-t)
-- Linear morph between two given curves
data (Curve c, Curve d) => CurveInterpol c d = CrvInterpol c d Float
instance (Curve c, Curve d) => Curve (CurveInterpol c d) where
evalCurve (CrvInterpol crv1 crv2 t0) t =
line (evalCurve crv1 t) (evalCurve crv2 t) t0
-- Fixing a surface’s u parameter gives a v-curve in the surface
data (Surface s) => CurveKeepU s = CrvKeepU s Float
instance (Surface s) => Curve (CurveKeepU s) where
evalCurve (CrvKeepU srf u0) t = evalSurf srf u0 t
-- Fixing a surface’s v parameter gives a u-curve in the surface
data (Surface s) => CurveKeepV s = CrvKeepV s Float
instance (Surface s) => Curve (CurveKeepV s) where
evalCurve (CrvKeepV srf v0) t = evalSurf srf t v0
107
108
ANHANG A. HASKELL-CODE
-- Cubic Bezier curve from four control points
data CurveBezier = CrvBezier Vector3 Vector3 Vector3 Vector3
instance Curve CurveBezier where
evalCurve (CrvBezier p0 p1 p2 p3) t = line p012 p123 t
where p01 = line p0 p1 t
p12 = line p1 p2 t
p23 = line p2 p3 t
p012 = line p01 p12 t
p123 = line p12 p23 t
-- Standard circle
data CurveCircle = CrvCircle
instance Curve CurveCircle where
evalCurve (CrvCircle) t = V3 (cos u) (sin u) 0.0
where u=t*2.0*pi
-- Space ellipse for midpoint and two direction vectors
data CurveEllipse = CrvEllipse Vector3 Vector3 Vector3
instance Curve CurveEllipse where
evalCurve (CrvEllipse m v w) t = add m (add (mult (cos u) v)
(mult (sin u) w))
where u=t*2.0*pi
-- *****************************************
-- ** SURFACES
-- *****************************************
-- Identical xy rectangle
data SurfXY = SrfXY
instance Surface SurfXY where
evalSurf (SrfXY) u v = V3 u v 0.0
-- Make a transformed surface a surface
data (Transform t, Surface s) => SurfTransform t s = SrfTransform t s
instance (Transform t, Surface s) => Surface (SurfTransform t s) where
evalSurf (SrfTransform trans srf) u v = evalTrans trans (evalSurf srf u v)
-- Make a function a surface
data SurfFct = SrfFct (Float->Float->Vector3)
instance Surface SurfFct where
evalSurf (SrfFct f) u v = f u v
-- Make a parameterized curve a surface
data (Curve c) => SurfParamCrv c = SrfParamCrv (Float->c)
instance (Curve c) => Surface (SurfParamCrv c) where
evalSurf (SrfParamCrv f) u v = evalCurve (f u) v
-- Bicubic Bezier tensor product surface from sixteen control points
data SurfBezier = SrfBezier Vector3 Vector3 Vector3 Vector3
Vector3 Vector3 Vector3 Vector3
Vector3 Vector3 Vector3 Vector3
Vector3 Vector3 Vector3 Vector3
instance Surface SurfBezier where
evalSurf (SrfBezier p00 p01 p02 p03
p10 p11 p12 p13
p20 p21 p22 p23
p30 p31 p32 p33) u v = evalCurve (
CrvBezier (evalCurve (CrvBezier p00 p01 p02 p03) u)
(evalCurve (CrvBezier p10 p11 p12 p13) u)
A.1. DIE DATEI GENMOD.HS
(evalCurve (CrvBezier p20 p21 p22 p23) u)
(evalCurve (CrvBezier p30 p31 p32 p33) u)
109
) v
-- Full sphere in polar coordinate parameterization
data SurfSphere = SrfSphere
instance Surface SurfSphere where
evalSurf (SrfSphere) u v = V3 (cosu*cosv) (cosu*sinv) (sinu)
where sinu=sin (pi*(u-0.5))
cosu=cos (pi*(u-0.5))
sinv=sin (2*pi*v)
cosv=cos (2*pi*v)
-- One sixth of a sphere, a quadriliteral with midpoint (1,0,0)
data SurfSphereSegment = SrfSphereSegment
instance Surface SurfSphereSegment where
evalSurf (SrfSphereSegment) u v =
normalize (V3 coss sins ((-coss)+v*2.0*coss))
where s=0.5*pi*(u-0.5)
coss = cos s
sins = sin s
-- Interpolate linearly between corresponding points of given curves
data (Curve c, Curve d) => SurfInterpolCrv c d = SrfInterpolCrv c d
instance (Curve c, Curve d) => Surface (SurfInterpolCrv c d) where
evalSurf (SrfInterpolCrv crv1 crv2) u v =
evalCurve (CrvInterpol crv1 crv2 u) v
-- Bilinear Coons surface between four border curves, but corners must
-- coincide: c0v(0)=cu0(0), c0v(1)=cu1(0), c1v(0)=cu0(1), c1v(1)=cu1(1)
data (Curve a, Curve b, Curve c, Curve d) =>
SurfCoons a b c d = SrfCoons a b c d
instance (Curve a, Curve b, Curve c, Curve d) =>
Surface (SurfCoons a b c d) where
evalSurf (SrfCoons c0v c1v cu0 cu1) u v =
sub ( add (mult u1 (evalCurve c0v v))
(add (mult u (evalCurve c1v v))
(add (mult v1 (evalCurve cu0 u))
(mult v (evalCurve cu1 u)))))
( add p00 (add p01 (add p10 p11)))
where u1=1.0-u
v1=1.0-v
p00=mult (u1*v1) (evalCurve c0v 0.0)
p01=mult (u1*v ) (evalCurve cu1 0.0)
p10=mult (u *v1) (evalCurve cu0 1.0)
p11=mult (u *v ) (evalCurve c1v 1.0)
-- Wire Product operator from Snyder, 3.2.1.2, p. 64
data (Curve a, Curve b) => SurfWireProd a b = SrfWireProd a b
instance (Curve a, Curve b) => Surface (SurfWireProd a b) where
evalSurf (SrfWireProd cross wire) u v = V3 (x p) (y p) (y crss_v)
where tang
= tangent
wire u
wire_u = evalCurve wire u
crss_v = evalCurve cross v
left
= V3 (-(y tang)) (x tang) 0.0
p
= add (mult (x crss_v) left) wire_u
110
ANHANG A. HASKELL-CODE
-- Rail Product operator from Snyder, 3.2.1.3, p. 66
data (Curve a, Curve b, Curve c) => SurfRailProd a b c = SrfRailProd a b c
instance (Curve a, Curve b, Curve c) => Surface (SurfRailProd a b c) where
evalSurf (SrfRailProd cross rail1 rail2) u v = V3 (x p) (y p) (y cross_u)
where cross_u = evalCurve cross u
rail1_v = evalCurve rail1 v
rail2_v = evalCurve rail2 v
t
= sub rail2_v rail1_v
m
= mult 0.5 (add rail2_v rail1_v)
p
= add m (mult (0.5*(x cross_u)) t)
-- *****************************************
-- ** TRANSFORMS
-- *****************************************
-- Make concatenated transforms a transform
data (Transform t0, Transform t1) => TransformCmps t0 t1 = TrnCmps t0 t1
instance (Transform t0, Transform t1) => Transform (TransformCmps t0 t1)
where
evalTrans (TrnCmps trans1 trans0) p =
evalTrans trans1 (evalTrans trans0 p)
-----
infix notation for transform composition
infixr 8 ř
(ř)::(Transform t0, Transform t1) => t0 -> t1 -> TransformCompose t0 t1
t0 ř t1 = TrnCmps t0 t1
-- Make a function a transform
data TransformFct = TrnFct (Vector3->Vector3)
instance Transform TransformFct where
evalTrans (TrnFct f) p = f p
-- Translation
data TransformTranslate = TrnTranslate Vector3
instance Transform TransformTranslate where
evalTrans (TrnTranslate v) p = add p v
-- Scaling
data TransformScale = TrnScale Vector3
instance Transform TransformScale where
evalTrans (TrnScale v) p = V3 ((x p)*(x v)) ((y p)*(y v)) ((z p)*(z v))
-- Rotation about the Z axis in the XY plane
data TransformRotXY = TrnRotXY Float
instance Transform TransformRotXY where
evalTrans (TrnRotXY w) p = V3 (c*(x p)-s*(y p)) (s*(x p)+c*(y p)) (z p)
where s = sin (2*pi*w)
c = cos (2*pi*w)
-- Rotation about the X axis in the YZ plane
data TransformRotYZ = TrnRotYZ Float
instance Transform TransformRotYZ where
evalTrans (TrnRotYZ w) p = V3 (x p) (c*(y p)-s*(z p)) (s*(y p)+c*(z p))
where s = sin (2*pi*w)
c = cos (2*pi*w)
A.1. DIE DATEI GENMOD.HS
111
-- Rotation about the Y axis in the ZX plane
data TransformRotZX = TrnRotZX Float
instance Transform TransformRotZX where
evalTrans (TrnRotZX w) p = V3 (c*(x p)+s*(z p)) (y p) (c*(z p)-s*(x p))
where s = sin (2*pi*w)
c = cos (2*pi*w)
-- *****************************************
-- ** SERVICE FUNCTIONS AND CONSTANTS
-- *****************************************
diffQuot_h::Float
diffQuot_h=0.005
diffQuot_2h=0.5/diffQuot_h
diffQuot_h2=(1.0/diffQuot_h)/diffQuot_h
-- Base of it all: The straight affine line segment
line :: Vector v => v->v->Float->v
line a b t = add (mult (1-t) a) (mult t b)
-- XY-Heightfield over [-1,1]x[-1,1]
heightfield a00 a01 a02 a03
a10 a11 a12 a13
a20 a21 a22 a23
a30 a31 a32 a33
= SrfBezier (V3 a a a00) (V3 b a a01)
(V3 a b a10) (V3 b b a11)
(V3 a c a20) (V3 b c a21)
(V3 a d a30) (V3 b d a31)
where a= 1.0
b= 1.0/3.0
c=(-1.0)/3.0
d=(-1.0)
(V3
(V3
(V3
(V3
c
c
c
c
a
b
c
d
a02)
a12)
a22)
a32)
(V3
(V3
(V3
(V3
d
d
d
d
a
b
c
d
a03)
a13)
a23)
a33)
-- XY-Heightfield over [0,1]x[0,1] (standard surface parameter space)
heightfieldNrm a00 a01 a02 a03
a10 a11 a12 a13
a20 a21 a22 a23
a30 a31 a32 a33
= SrfBezier (V3 a a a00) (V3 b a a01) (V3 c a a02) (V3 d a a03)
(V3 a b a10) (V3 b b a11) (V3 c b a12) (V3 d b a13)
(V3 a c a20) (V3 b c a21) (V3 c c a22) (V3 d c a23)
(V3 a d a30) (V3 b d a31) (V3 c d a32) (V3 d d a33)
where a= 1.0
b= 2.0/3.0
c= 1.0/3.0
d= 0.0
112
ANHANG A. HASKELL-CODE
-- *****************************************
-- ** SAMPLING OF CURVES AND SURFACES
-- *****************************************
-- Transform a curve into a list of points on the curve
-- Standard curve interval for regular sampling: [0,1]
sampleCurve::(Curve c) => Int->c->[Vector3]
sampleCurve nn crv = [ evalCurve crv (fromIntegral(i)*dt) | i<-[0..nn] ]
where dt = 1.0/(fromIntegral(nn))
-- Transform a surface into a list of list of points on the surface
-- Standard surface interval for regular sampling: [0,1]x[0,1]
sampleSurf::(Surface s) => Int->Int->s->[[Vector3]]
sampleSurf nu nv srf = [ [ evalSurf srf (fromIntegral(i)*du)
(fromIntegral(j)*dv)
| i<-[0..nu] ] | j<-[0..nv] ]
where du = 1.0/(fromIntegral(nu))
dv = 1.0/(fromIntegral(nv))
-- Show a curve
in the format "crv_0=[(x,y,z),...]"
showSurface::(Surface s) => String->Int->Int->s->String
showSurface name nu nv srf = name ++ "=" ++ (show (sampleSurf nu nv srf))
++ "\n"
-- Show a surface in the format "srf_0=[ [(x,y,z),...], ...]"
showCurve::(Curve c) => String->Int->c->String
showCurve name nn crv = name ++ "=" ++ (show (sampleCurve nn crv)) ++ "\n"
-- Sample a function [0,1]->Curve and show the curves
showCurves::(Curve c) => String->Int->Int->(Float->c)->String
showCurves name nn steps crvFct = showCrv (steps-1) where
dt = (1.0/fromIntegral(steps-1))
showCrv (-1) = ""
showCrv n
= let fn=name++(show n)
t =dt*fromIntegral(n) in
(showCurve fn nn (crvFct t)) ++ (showCrv (n-1))
-- Sample a function [0,1]->Surface and show the surfaces
showSurfaces::(Surface s) => String->Int->Int->Int->(Float->s)->String
showSurfaces name nu nv steps srfFct = showSrf (steps-1) where
dt = (1.0/fromIntegral(steps-1))
showSrf (-1) = ""
showSrf n
= let fn=name++(show n)
t =dt*fromIntegral(n) in
(showSurface fn nu nv (srfFct t)) ++ (showSrf (n-1))
A.2. DIE DATEI VECTORS.HS
A.2
113
Die Datei Vectors.hs
module Vectors(
Vector(add,sub,prod,cross,mult,absLen,normalize,zeig,x,y,z),
Vector3(V3),
zeigZahl,
zeigListe) where
class Vector
add
sub
prod
cross
mult
absLen
normalize
zeig
x
y
z
a where
:: a->a->a
:: a->a->a
:: a->a->Float
:: a->a->a
:: Float->a->a
:: a->Float
:: a->a
:: a->String
:: a->Float
:: a->Float
:: a->Float
data Vector3 = V3 Float Float
instance Vector Vector3 where
add
(V3 ax ay az) (V3 bx
sub
(V3 ax ay az) (V3 bx
prod (V3 ax ay az) (V3 bx
cross (V3 ax ay az) (V3 bx
Float
by
by
by
by
bz)
bz)
bz)
bz)
=
=
=
=
(V3 (ax+bx) (ay+by) (az+bz))
(V3 (ax-bx) (ay-by) (az-bz))
(ax*bx)+(ay*by)+(az*bz)
(V3 (ay*bz-az*by)
(az*bx-ax*bz) (ax*by-ay*bx))
mult
a (V3 bx by bz) = (V3 (a*bx) (a*by) (a*bz))
zeig (V3 ax ay az)
=
"(" ++ (show ax) ++ "," ++ (show ay) ++ "," ++ (show az) ++ ")"
absLen
(V3 ax ay az)
= sqrt ( (ax*ax) + (ay*ay) + (az*az) )
normalize (V3 ax ay az)
= (V3 (l*ax) (l*ay) (l*az))
where k = absLen (V3 ax ay az)
l = case k of
0 -> 0
_ -> (1/k)
x
(V3 ax ay az)
= ax
y
(V3 ax ay az)
= ay
z
(V3 ax ay az)
= az
114
ANHANG A. HASKELL-CODE
A.2. DIE DATEI VECTORS.HS
115
116
ANHANG A. HASKELL-CODE
Herunterladen