Rudi's Homepage - Lightflow

EnglishDeutsch

Einführung

Wenn Sie schon Bekanntschaft mit Renderern wie PovRay gemacht haben wird Ihnen das Konzept von Lightflow etwas fremd vorkommen: LightFlow definiert sich als Erweiterung zu objekt-orientierten Sprachen (momentan Python und C++) - aber dieser Ansatz wurde auch schon für RenderMan genutzt, womit so schöne Filme wie "A toys story" gerendert wurden. Lightflow weist in der Tat sehr viele ähnlichkeiten mit dem RenderMan-Interface auf, so das es zumindest als ein stark von RenderMan geprägtes C++- bzw. Python-Interface gelten kann.
Das Proxy-Konzept von Lightflow.
Das Proxy-Konzept von Lightflow.
Das Basiskonzept von Lightflow ist die Kommunikation mit dem eigentlichen Renderer über einen Proxy (etwas, was in Ihrem Auftrag etwas tut - z.B. Webseiten aus dem Netz laden). Die Vorteile sind im Einzelnen:
  1. Eine klare Trennung zwischen Renderer und dem Programm durch das Proxy-Konzept.
  2. Das Rendering kann durch Wahl eines anderen Proxies auf andere Maschinen verlagert werden; somit kann auch eine Parallelverarbeitung erreicht werden.
  3. Man kann die volle Leistungsfähigkeit der "Gastgebersprachen", z.B. für numerische Berechnungen, nutzen.
Leider ist die freie Version von Lightflow auf lokales Rendern beschränkt - was aber die einzige Einschränkung war. Auf dieser Seite finden sie weitere Informationen zur Nutzung von Lightflow, wobei der Schwerpunkt auf dem C++-Interface liegt - zum einen ist C++ doch bekannter als Python, zum anderen ist sie auch numerisch bedeutend effizienter.
Zuletzt aktualisiert: 2018.06.18 6:45 PM

Beispiele

Weiter unten finden Sie einige Beispielbilder (die meisten Beispiele sind vom Lightflow-Autor selbst) samt ihren Quellen.

ocean.py
ocean.py
mechanic.py
mechanic.py
Terminator1.py
Terminator1.py
Terminator2.py
Terminator2.py
bspline3.py
bspline3.py
bspline4.py
bspline4.py
tree.tar.gz (C++!)
tree.tar.gz (C++!)
claud.py
claud.py


Zuletzt aktualisiert: 2018.06.18 6:45 PM

Installation

Lightflow kann über seine Homepage unter http://www.lightflowtech.com bezogen werden. Da es mit dem C++-Interface einige Probleme gab, habe ich die Linux-Version con LightFlow und die modifizierten Header-Dateien gespiegelt:
  1. Lightflow für Linux (Lightflow.tar.bz2).
  2. Lightflow C++-include-Dateien (angepaßt für Installation unter /usr/include/Lightflow) [Lightflow_Include.tar.bz2].

Installationsanweisung.

  1. Entpacken Sie das Basispaket, z.B. nach /usr/local.
  2. Das Python-Interface benötigt Python Ver. >= 1.5, welches ggf. noch installiert werden muß.
  3. Kopieren oder verlinken sie LightflowPM.so nach /usr/lib/phython1.5/site-packages (oder woimmer das Verzeichnis bei Ihnen ist):
     ln -s /usr/local/LightFlow/LightflowPM.so /usr/lib/python1.5/site-packages  
       
  4. Das Gleiche für die C++-Bibliothek:
     ln -s /usr/local/LightFlow/Lightflow/libLightflow.so /usr/lib
       
  5. Installieren sie den Inhalt von Lightflow_Include.tar.bz nach /usr/include/Lightflow.
  6. Fügen Sie zum Konfigurationsskript Ihrer Shell (z.B. ~/.bashrc) die Umgebungsvariable LIGHTFLOWPATH mit dem Lightflow-Basisverzeichnis hinzu:
      export LIGHTFLOWPATH=/usr/local/LightFlow
       
  7. Sehen Sie die Dokumentationen unter PM/doc (Python) und CS/doc (C++) durch, Beispiele für Python finden Sie unter PM/Examples.
  8. Nun sollten die Beispiele von dieser Seite funktionieren (Python-Skripte müssen vorher ausführbar gemacht werden, bei den C++-Beispielen sollte es ein make machen).


Zuletzt aktualisiert: 2018.06.18 6:45 PM

C++-Einstiegstutorial

Erste Schritte.

In diesem Tutorial werde ich Ihnen zeigen, wie C++-Anbindung von Lightflow genutzt werden kann. Ausgehend von einer sehr einfachen Szene werden schrittweise mehr und mehr Techniken eingef?hrt. Dabei gehe ich davon aus, das sie eine UNIX-Umgebung mit den n?tigen Entwicklungs-Tools (C++-Compiler, Make usw.) verwenden. Legen Sie zuerst f?r das Projekt ein neues Unterverzeichnis an. Da wir ein C++-Programm schreiben, beginnen wir zur Vereinfachung des Compilervorgangs mit dem Makefile. ?ffnen Sie ihren bevorzugten Editor und geben Sie das unten angegebene Makefile (nat?rlich ohne die Erl?uterungen!) ein und speichern sie es als Makefile.

Das Makefile.
Das Makefile.
Es ist eigentlich sehr einfach aufgebaut: Die Makrodefinitionen am Anfang legen Makros f?r den Aufruf der verwendeten Tools und deren Optionen fest. Dies erleichtert Anpassungen an andere Plattformen oder Tools betr?chtlich. Der wichtigste Teil eines Makefiles sind die Regeln zum Erzeugen der Ziele ( „creation rules” ). Diese beginnen mit der Angabe des zu erzeugenden Ziels gefolgt von einem Doppelpunkt (z.B. „simplescene:” f?r das Programm). Direkt dahinter folgen in der gleichen Zeile die Abh?ngigkeiten des Ziels (z.B. Kuchen: Eier, Butter, Mehl). Make wird das Ziel neu erstellen wenn es ?lter als seine Abh?ngigkeiten ist. So wird das Programm simplescene neu erstellt, wenn sein Quelltext main.cpp seit der letzten ?bersetzung editiert worden ist. Die nun folgenden Zeilen (die ?brigens alle mit einem <Tab> beginnen m?ssen!) legen die zur Erstellung des Ziels n?tigen Aktionen fest. Standardm??ig beginnt Make, wenn es ohne ein Ziel aufgerufen wird, mit dem ersten angef?hrten Ziel - in unserem Beispiel der gew?nschten Grafikdatei ball.jpg. Da LightFlow nur Bilder in einem internen Format und im mittlerweile etwas in Vergessenheit geratenem .tga-Format erzeugen kann, mu? die JPEG-Datei mittels des m?chtigen convert-Tools aus dem ImageMagick-Toolkit aus einer TGA-datei erstellt werden. Bei der Gelegenheit sollte man sich auch die man-Pages von convert ansehen, um einen ?berblick ?ber die M?glichkeiten dieses kleinen Programms zu erhalten. Somit ist die TGA-Datei die Abh?ngigkeit der JPEG-Datei. Die TGA-Datei wiederum wird durch den Aufruf des C++-Programms simplescene erzeugt, welches wiederum mittels des C++-Compilers aus seinem Quelltext main.cpp erzeugt wird (wichtig ist dabei den Linker-Flag -lLightflow zu setzen!).

Das Grundger?st.

Nun zum C++-Code des Programms. Erstellen Sie eine neue Datei main.cpp und geben Sie ihr folgenden Inhalt:

 #include <Lightflow/LfLocalSceneProxy.h>
 #include <math.h>

 int main()
 {
    // create a new proxy
    LfLocalSceneProxy *s = new LfLocalSceneProxy();
    // this is a container object for storing the arguments passed to
    // the renderer via the proxy
    LfArgList list;

    return 0;
 }
  
Zu Beginn werden die Mathematikbibliothek und die Headerdatei Lightflow/LfLocalSceneProxy.h, die die meisten hier ben?tigten Objekte deklariert, eingebunden. In der main-Prozedur wird zuerst ein Proxy-Objekt s erzeugt. Wie der Klassenname verr?t, ist es ein Proxy zur Kommunikation mit einer lokalen Renderer-Instanz. Dem Konstruktor kann noch die Gr??e des Texturpuffers ?bergeben werden, der aber f?r dieses Beispiel auf seiner Standardgr??e belassen wird. Das n?chste erzeugte Objekt,list, dient als Containerobjekt zur ?bergabe von Parametern an den Renderer ?ber das Proxy-Objekt. Wie Sie sp?ter sehen werden ist es so m?glich, nur die gew?nschten Parameter zu ?bergeben.

Am Anfang war das Licht.

Was ist ein Philosoph? Jemand, der stundenlang in einem finsteren Raum ?ber dessen Inhalt schwadroniert. Was ist ein Ingenieur? Jemand, der diese Frage mit dem Dr?cken des Lichtschalter beantwortet. Genug gel?stert - aber es sollte klar sein, das die Existenz einer Lichtquelle unabdingbar f?r die Sichtbarkeit von Objekten ist. Beginnen wir also unsere Szene mit einem neuen Punktlicht. F?gen sie die folgenden Zeilen direkt hinter der Erzeugung des list-Objektes ein:

   // define a light
   list.Reset();
   list << "position" <<   LfVector3(-4,-7,6);
   list <<   "color" <<   LfColor(450*1.0,450*1.0,450*0.8);
   LfInt light = s->NewLight("soft",list);
   s->LightOn(light); 
  
Schritt-f?r-Schritt: Zuerst wird das Container-Objet zur?ckgesetzt. Dies entfernt die Daten der letzten Parameterisierung und sollte deshalb vor jeder Parametersierung ausgef?hrt werden. Danach werden zwei Attribute der Lichtquelle spezifiziert: ihre Position und ihre Farbe. Die Position ist ein Koordinatedtripel des 3D-Raums, die Farbe wird als gewichteter RGB-Wert ?bergeben. Je h?her die Werte, um so st?rker ist die Lichtquelle. In unserem Beispiel wurde der Farbwert (1.0,1.0,0.8) von der Intensit?t (der Faktor 450) separiert. Position und Farbe werden als LfVector3-Objekte ?bergeben. Sp?ter werde ich noch auf andere von LightFlow bereitgestellte Datentypen eingehen, aber mehr wird im Moment nicht ben?tigt. In der n?chsten Zeile wird dem Renderer ?ber den Proxy der Befehl gegeben, das Licht mit den vorher definierten Parametern zu erzeugen. Dazu offeriert der Proxy die NewLight-Methode. Diese hat zwei Argumente: Der Typ des Lichts ("soft" w?rde eine ausgedehnte und somit weiche Schatten werfende Lichtquelle erzeugen). Diese Informationen k?nnen Sie der HTML-Dokumentation von Lightflow entnehmen, die sich im Unterverzeichnis CS der Lightflow-Dokumentation befindet. Diese sollte am besten unter /usr/doc/Lightflow installiert werden und als Link im Browser ihrer Wahl aufgenommen werden. Auch wenn Sie unter C++ programmieren wollen, sollten Sie sich trotzdem die Dokumentation zum Python-Interface ansehen, da sie dort mehr Beispiele finden werden. Nun aber zur?ck zum Quelltext: Die NewLight()-Methode liefert einen Integer-Handle f?r das erzeugte Licht zur?ck welches Sie gleich dazu benutzen werden, um das Licht mit der Methode LightOn zu aktivieren. Die Aktivierung von Lichtquellen ist sehr n?tzlich f?r Animationen.

Material f?r den Ball.

Nachdem wir die totale Finsternis durch das Hinzuf?gen eines Lichts ?berwunden haben fehlt nur noch etwas zum Betrachten selbst. Fangen wir mit einem Plastikball an. F?r diesen ben?tigt Lightflow folgende Informationen:

  1. Die Geometrie (Position,Form,Orientierung,...)
  2. Das Aussehen (gl?nzend,stumpf,Farbe,Transparenz,Brechungsindex,...)
Traditionell wird das Aussehen eines Objektes durch sein Material verk?rpert. Ein h?lzerner Ball ist schlie?lich nur eine Kugel aus dem Material "Holz". In Lightflow werden die Materialeigenschaften ?ber diese Gruppen festgelegt: Material Das „Material” definiert die Interaktion zwischen Licht und Materie an deren Oberfl?che. Lightflow bietet verschiedene Materialklassen (physical,standard) mit unterschiedlichen St?rken und Schw?chen. Interior Das „Innere” definiert -wie der Name schon sagt- die Interaktion zwischen Licht und virtueller Materie im Inneren des K?rpers. Damit ist nicht die Brechung gemeint (diese findet an der Grenzschicht statt und ist somit Materialeigenschaft), sondern z.B. Streuung in dampfges?ttigter oder staubiger Luft. Pattern „Pattern” bzw. Muster dienen zur Variation von Eigenschaften der simulierten Materie in Anh?ngigkeit von ihrer Position. Ein bekanntes Beispiel sind Texturen - dort variieren sie Farbeigenschaften. Aber sie k?nnen auch die Dichte, Oberfl?chen-Normalenvektoren (Bump-Mapping, wird weiter unten erl?utert) oder die Form der Oberfl?che selbst ?ndern. Die Variation kann durch Vorgabe von St?tzstellen numerisch oder in Form mathematischer Gleichung und in Form von ?berlagerungen von Variationen beschrieben werden. Beginnen wir mit einem einfachen Plastikball:
    // define a material (standard material, there are others)
    list.Reset();
    // ambient color
    list << "ka" << LfColor(0,0,0.5);
    // reflection color
    list << "kc" << LfColor(1,0.5,0.5);
    // diffuse reflection factor
    list << "kd" << 0.5;
    // specular reflection smoothness (0-polished,1-plastic)
    list << "km" << 0.1;
    // LfInt is a long int representing a handle for the material
    LfInt plastic = s->NewMaterial("standard",list);
  
F?r das Material habe ich die f?r Computergrafik einfacher zu handhabende standard-Klasse genommen. Der Glattheitsfaktor km und die St?rke des diffus reflektierten Anteils kd werden gleichzeitig f?r das lokale und globale (Radiosity - wenn aktiv!) Beleuchtungsmodell verwendet. F?r das lokale Beleuchtungsmodell werden zus?tzlich die St?rke des reflektierten ambienten Lichts (definiert die Grundfarbe ohne direkte Beleuchtung), und der Farbeinflu? des direkt reflektierten Lichts im lokalen Beleuchtungsmodell (m.E. Phong) angegeben (f?r das globale Beleuchtungsmodell werden andere Parameter verwendet - wie bei Raytracern ?blich!)

Endlich ein Ball.

Dieser Teil ist ziemlich einfach: wir f?gen einfach eine Kugel zur Szene hinzu. Diese wird im Ursprung des Koordinatensystems erzeugt; wenn diese an einer anderen Stelle erscheinen soll, mu? zuerst der Ursprung entsprechend transformiert werden. Nach der Erzeugung m?ssen die Objekte expliziet in die Liste der darzustellenden Objekte aufgenommen werden. Dazu dient die Methode AddObject, welcher der Handle des Objekts als Argument ?bergeben wird. Auf diese Art und Weise k?nnen tempor?re Objekte, wie sie f?r Konstruktionstechniken wie CSG ben?tigt werden, erzeugt aber nicht dargestellt werden. Die Zuweisung zum Material geschieht automatisch, wenn die Objekterzeugung in einem entsprechenden MaterialBegin-Block steht. ?quivalent kann man (s. Dokumentation) mit einem LightBegin-Block die Wirkung von Lichtquellen auf bestimmte Objekte begrenzen.

    s->MaterialBegin(plastic);
    list.Reset();
    list << "radius" << 1.0;
    LfInt ball = s->NewObject("sphere",list);
    s->AddObject(ball);
    s->MaterialEnd();
  

Zum fotografieren ben?tigt man eine Kamera.

Dies ist in der virtuellen Welt nicht anders. Deshalb mu? noch eine Kamera oder besser gesagt: ein Nachverarbeitungssystem angegeben werden. Dies kann man sich wie ein Flie?band oder eine Kette vorstellen, an deren Anfang die virtuelle Kamera und an deren Ende das Dateiausgabe-Plugin „tgasaver” steht. Die dazwischenliegenden Plugins (in LightFlow Imager genannt) k?nnen z.B. Tiefensch?rfe-Effekte hinzuf?gen, die Wirkung der K?rnigkeit von fotografischen Filmen oder Refexionseffekte im Linsensystem ( „Lens flares” ) hinzuf?gen. F?r unser Beispiel nehmen wir nur den TGA-Saver.

    list.Reset();
    list << "file" << "ball.tga";
    LfInt saver = s->NewImager("tga-saver",list);
  
Der letzte n?tige Schritt ist die Erzeugung einer Kamera im Imager-Block. F?r die Kamera m?ssen mindestens die Parameter „Position” und der anvisierte Punkt angegeben werden (aim).
    // specify the rendering context
    s->ImagerBegin(saver);
    list.Reset();
    // camera position
    list << "eye" << LfPoint(0,-4,0);
    // point to aim at
    list << "aim" << LfPoint(0,0,0);
    LfInt camera = s->NewCamera("pinhole",list);
    s->ImagerEnd();
   
Anschlie?end folgt das Kommando zum Starten der Berechnung. Mit der Methode „s->Radiosity()” kann zudem die Verwendung des Radiosity-Verfahrens zur Ber?cksichtigung der diffusen Reflektion vor s->Render() aktiviert werden.
    // start rendering
    s->Render(camera,300,300);
    delete s;
    return 0;
  

Let it run!

Das Makefile und der Quellcode finden sich hier noch einmal zusammengefa?t. Nach der Eingabe von „make” sollte folgendes passieren:

   lightflow/simplescene> make
     g++  -lLightflow main.cpp -o simplescene
     simplescene
     
     Lightflow Rendering Tools
     Copyright (c) 1995-2000 by Jacopo Pantaleoni. All rights reserved


     Pinhole Camera On
     Objects : 1
     LfSoftLight::Init()
     00:00:01 - cell 418176 / 418176

     Rendering 300 x 300 pixels
     00:00:01 - 87.1%
     convert ball.tga ball.jpg
   
Anschlie?end sollten Sie folgendes Bild in Ihrem Verzeichnis vorfinden:
Ein Plastikball.
Ein Plastikball.
So viel Arbeit f?r eine Plastikball? Keine Sorge, der gr??te Teil des Programs beinhaltet unvermeidbare Initialisierungen - danach geht es deutlich z?giger voran. Im n?chsten Schritt soll der Ball aus Metall gefertigt sein:
    list.Reset();
    list << "kr" << LfVector3(0.6,0.3,0.3);
    list << "kd" << 0.3;
    list << "km" << 0.3;
    list << "shinyness" << 0.8;
    list << "fresnel" << LfInt(1) << LfFloat(0.5) << LfFloat(0.5);
    list << "caustics" << LfInt(1) << LfInt(1);
    LfInt metal = s->NewMaterial("physical",list);

    list.Reset();
    list << "basis" << "sin";
    list << "scale" << 0.6;
    list << "depth" << 0.2;
    list << "turbulence.omega" << 0.5 << 0.7;
    list << "turbulence.octaves" << LfInt( 6 );
    LfInt bump = s->NewPattern( "multifractal", list );

    list.Reset();
    list << "kr" << LfVector3(0.3,0.3,0.5);
    list << "kd" << 0.3;
    list << "km" << 0.3;
    list << "shinyness" << 0.8;
    list << "fresnel" << LfInt(1) << LfFloat(0.5) << LfFloat(0.5);
    list << "caustics" << LfInt(1) << LfInt(1);
    list << "displacement" << bump;
    LfInt bumpmetal = s->NewMaterial("physical",list);
  
Das erste Material, metal, ist von der Materialklasse physical abgeleitet. Diese ber?cksichtigt selbst den Fresnell-Effekt (Abh?ngigkeit des Brechungsindex vom Einfallswinkel)- Lightflow wurde halt f?r wissenschaftliche Visualisierungen entworfen. F?r das zweite Material wurde die Oberfl?chenform durch ein multifraktales Muster variiert. Solche Verformungen werden von vielen Programmen, heutzutage selbst von Grafikkarten in Hardware als sog. Bumpmapping unterst?tzt. Den Unterschied zeigt das folgende Bild:
Real surface displacement (left) and bump mapping (right).
Real surface displacement (left) and bump mapping (right).
Bumpmapping variiert nur die Normalenvektoren der Oberfl?che; da diese Vektoren im Brechungs- und Reflexionsgesetz der linearen Optik eine gro?e Rolle spielen, erscheint die Oberfl?che verformt. Allerdings wird durch Bump-Mapping nicht die Form des eigentlichen Objekts ver?ndert - trotz der Oberfl?chenstruktur ist der Rand einer Kugel danach noch immer perfekt kreisf?rmig. Dies soll das n?chste Beispiel verdeutlichen: Die Szene soll zwei Kugeln beinhalten, wobei die linke mit Bumpmapping und die rechte mit einer echten verformten Oberfl?che gerendert wird. Damit diese beiden Kugeln nicht direkt ?bereinanderliegen, m?ssen sie bzw. ihr Koordinatensystem verschoben werden. Die Abbildung auf das neue Koordinatensystem wird durch verschachtelte Transformationsbl?cke angegeben. Lightflow kennt die folgenden Elementartransformationen: Translation trs.Translation(LfVector3(x,y,z)) Skalierung trs.Scaling(LfVector3(xscale,yscale,zscale)) Rotation
um die X-Achse trs.RotationAroundX(angle_in_radians) um die Y-Achse trs.RotationAroundY(angle_in_radians) um die z-Achse trs.RotationAroundZ(angle_in_radians)
Komplexe Operationen m?ssen durch Verschachtelungen dieser Bl?cke angegeben werden:
  s->TransformBegin(trs.Translation(LfVector3(-1.7,0,1.3)));
   s->TransformBegin(trs.RotationAroundZ(-1.5));
   ...
   s->TransformEnd();
  s->TransformEnd();
 
Zur?ck zur Szene:
    // the next two are made from bump metal
    s->MaterialBegin(bumpmetal);
    // the first ball to the left should use bump mapping
    s->TransformBegin(trs.Translation(LfVector3(-1.7,0,1.3)));
    list.Reset();
    list << "radius" << 1.0;
    s->AddObject(s->NewObject("sphere",list));
    s->TransformEnd();
    s->TransformBegin(trs.Translation(LfVector3(1.7,0,1.3)));
    // ...a real surface!
    LfInt sphere = s->NewObject( "sphere",list);
    list.Reset();
    list << "surfaces" << sphere;
    list << "tolerance" << 0.02 << 0.1 << 0.05;
    s->AddObject( s->NewObject( "surface-engine", list ) );
    s->TransformEnd();
    s->MaterialEnd();
 
Wie man anhand des untenstehenden Bildes sieht, ist der Unterschied bemerkenswert. Wenn Sie das Muttern-Beispiel betrachten werden Sie sehen, das man die verformten Oberfl?chen auch f?r CSG-Operationen verwenden kann (so wurde das Gewinde „herausgeschnitten” !) Hier der Code.
Ball berechnet mit Bump Mapping (links) und echter Oberfl?chenvariation (rechts).
Ball berechnet mit Bump Mapping (links) und echter Oberfl?chenvariation (rechts).

Flyby

flyby.mpg
flyby.mpg
Zum Abschlu? m?chte ich Ihnen noch das Beispiel einer Animation zeigen. Ausgehend von der letzten Szene soll nach dem in der Illustration rechts dargestellten „Drehbuch” ein Kameravorbeiflug realisiert werden.
The movie's "plot".
The movie's "plot".
Die Kamera soll sich dabei auf einem Halbkreis bewegen und st?ndig einen Punkt zwischen den Kugeln anvisieren. Mit den mathematischen F?higkeiten von C++ ist dies kein Problem, wie der Quelltext zeigt. Aber auch das Makefile mu? ge?ndert werden, da nicht mehr ein einziges Bild sondern eine ganze Animation berechnet werden soll. Ein zus?tzliches „clean” -Ziel (Aufruf mit make clean sorgt daf?r, das nach der Berechnung die tempor?ren Einzelbilder gel?scht werden. Ein ganzes St?ck Arbeit - aber gelohnt hat es sich, oder?

Abschlu?

Lightflow ist ein au?ergew?hnlich leistungsf?higes Program. Die ber?cksichtigten optischen Effekte (Fresnell, Kaustiken, nichtisotropische Oberfl?chen), die Vielfalt an zur Verf?gung stehenden Primitiven (euklidische K?rper, Polygon-Netze, NURBS, B-Splines, Metaballs,...) und die Einbindung in objektorientierte Programmiersprachen machen es zu einer ausgezeichneten Wahl f?r wissenschaftliche Visualisierungen.# Die M?glichkeit in objektorientierten Sprachen zu schreiben erm?glicht die Definition komplexer Objekte, die auf hohem Abstraktionsniveau manipuliert werden k?nnen (z.B. ein menschliches Objekt mit der Methode „Schritt vor” ) und vor allem leicht wiederverwendet werden k?nnen. Schauen Sie sich - jetzt hoffentlich mit den n?tigen Grundkenntnissen gewappnet - die anderen Beispiele an. Viel Spa?!


Zuletzt aktualisiert: 2018.06.18 6:45 PM