|
Antwort |
Registriert seit: 4. Jun 2010 15.391 Beiträge |
#1
Este programa te permite dibujar un hipercubo de cualquier tamaño n, usando opengl. Hay varios modos de representación, estructura alámbrica, iluminación, estructura alámbrica con líneas ocultas, iluminación con líneas ocultas ... También hay una función que permite exportar los vértices calculados en un archivo * .inc que se puede usar con POV-Ray. unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, Opengl, StdCtrls, ComCtrls, Buttons; const MaxDim=10; //Dimension maximale de travail GL_POLYGON_OFFSET_FILL=$8037; type TVect=array[1..MaxDim] of Single; //vecteur TMatrix=array[1..MaxDim,1..MaxDim] of Single; //matrice TForm1 = class(TForm) Timer1: TTimer; Panel1: TPanel; ComboBox1: TComboBox; GroupBox1: TGroupBox; RadioGroup1: TRadioGroup; CheckBox1: TCheckBox; SpeedButton1: TSpeedButton; SpeedButton2: TSpeedButton; Panel2: TPanel; procedure FormCreate(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure ComboBox1Change(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); procedure SpeedButton2Click(Sender: TObject); private FGLRC:HGLRC; //contexte opengl FVertexes:array of TVect; //les sommets des faces FDim:Integer; //la dimension courante (inférieure à MaxDim) FMatrix:TMatrix; //la matrice des rotations procedure TrackBarChange(Sender:TObject); //événement lorsqu'un angle a changé procedure MakeMatrix; //génération de la matrice à partir des angles procedure DrawScene; //tracé de la scène procedure Animate; //procédure appelée pour changer les angles lorsque l'animation est activée public end; var Form1: TForm1; procedure glPolygonOffset(factor:GLfloat;units:GLfloat);stdc all;external Opengl32; //fonction Opengl non standard implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); //initialisation d'opengl et des diverses variables var PFd:TPixelFormatDescriptor; c:Integer; begin WindowState:=wsMaximized; ZeroMemory(@PFD,SizeOf(PFD)); PFD.nSize:=SizeOf(PFD); PFD.nVersion:=1; PFD.dwFlags:=PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER; //on se met en mode doublebuffered pour éviter les clignotements PFD.iPixelType:=PFD_TYPE_RGBA; PFD.cColorBits:=GetDeviceCaps(Canvas.Handle,BITSPI XEL); PFD.cAlphaBits:=8; //8 bits pour le canal alpha PFD.cDepthBits:=16; //16 pour le test de profondeur (Z-buffer) PFD.cStencilBits:=16; //on va effectuer de la géométrie cnstructive en utilisant le stencil buffer c:=ChoosePixelFormat(Canvas.Handle,@PFD); Assert(c0,'No valid pixel foprmat is supported: '+SysErrorMessage(GetLastError)); Assert(SetPixelFormat(Canvas.Handle,c,@PFD),'Could nt set the pixel format: '+SysErrorMessage(GetLastError)); //on active le pixel format du canvas pour supporter opengl FGLRC:=wglCreateContext(Canvas.Handle); //on crée un contexte opengl Assert(FGLRC0,'Couldnt create a valid GL context: '+SysErrorMessage(GetLastError)); Assert(wglMakeCurrent(Canvas.Handle,FGLRC),'Couldn t create use the GL context: '+SysErrorMessage(GetLastError)); //on active le contexte SetLength(FVertexes,0); ComboBox1Change(nil); //on initialise le premier cube end; procedure TForm1.Timer1Timer(Sender: TObject); begin DrawScene; //on trace la scène glFlush; //on notifie opengl que le tracé est fini SwapBuffers(Canvas.Handle); //on "swap" les 2 buffers (puisqu'on est en mode doublebuffered) end; procedure TForm1.FormDestroy(Sender: TObject); begin Assert(wglMakeCurrent(Canvas.Handle,0),'Couldnt unlock the GL context: '+SysErrorMessage(GetLastError)); //on désactive le contexte Assert(wglDeleteContext(FGLRC),'Couldnt delete the GL context: '+SysErrorMessage(GetLastError)); //on le détruit SetLength(FVertexes,0); end; function PowerOfTwo(n:Integer):Integer; //calcule une puissance de 2 (fonction peu optimisée mais peu utilisée) begin Result:=Round(Exp(Ln(2)*n)); end; procedure TForm1.ComboBox1Change(Sender: TObject); //Changement de dimension var a,b,c,n:Integer; t:TTrackBar; procedure MakeSubFaces; //On crée tous les quadrilatères possibles qui sont situés dans les plans de directions numéro a et b. Il y en a 2^(FDim-2) en tout. var i,j,u,v:Integer; begin for i:=0 to n-1 do begin //pour a et b fixés, il y a n quadrilatères possibles, correspondant à toutes les combinaisons de coordonnées 1 et -1 ZeroMemory(@FVertexes[c+4*i],SizeOf(TVect)); //dans les directions orthogonales à a et b. il suffit alors de prendre la représentation en base 2 de i variant ZeroMemory(@FVertexes[c+4*i+1],SizeOf(TVect)); //de 0 à n-1, et de dire que 0 correspond à la coordonnée -1, et 1 correspond à la coordonnée 1, ainsi on aura bien ZeroMemory(@FVertexes[c+4*i+2],SizeOf(TVect)); //toutes les combinaisons possibles de 1 et de -1 pour les directions orthogonales à a et b. ZeroMemory(@FVertexes[c+4*i+3],SizeOf(TVect)); u:=1; for j:=1 to FDim do begin //on parcourt toutes les coordonnées du vecteur if (j=a) or (j=b) then //si ce sont les coordonnées a ou b, on ne fait rien Continue; if (u and i)=u then //sinon, si le bit de i vaut 1, la coordonnée est 1, sinon elle vaut -1 v:=1 else v:=-1; FVertexes[c+4*i,j]:=v; //on modifie la coordonnée correspondante pour les 4 sommets du quadrilatère FVertexes[c+4*i+1,j]:=v; FVertexes[c+4*i+2,j]:=v; FVertexes[c+4*i+3,j]:=v; u:=u*2; end; FVertexes[c+4*i,a]:=1; //pour les coordonnées numéro a et b, on prend celles du carré usuel dans l'ordre: (1,1) (-1,1) (-1,-1) (1,-1) pour avoir une courbe fermée FVertexes[c+4*i+1,a]:=1; FVertexes[c+4*i+2,a]:=-1; FVertexes[c+4*i+3,a]:=-1; FVertexes[c+4*i,b]:=1; FVertexes[c+4*i+1,b]:=-1; FVertexes[c+4*i+2,b]:=-1; FVertexes[c+4*i+3,b]:=1; end; c:=c+4*n; //on a calculé en tout 4*n coordonnées supplémentaires, on décale l'indice de base end; begin Randomize; //réinitialisation de la fonction random FDim:=ComboBox1.ItemIndex+3; //calcul de la nouvelle dimension GroupBox1.DestroyComponents; //on détruit les trackbars de controle des angles t:=nil; for a:=0 to 3*(FDim-3)+2 do begin //on crée les nouvelles trackbar t:=TTrackBar.Create(GroupBox1); t.Align:=alTop; t.Top:=10000; t.Height:=26; t.Min:=0; t.Max:=3000; t.Position:=Random(3000); //position aléatoire t.TickMarks:=tmBoth; t.TickStyle:=tsNone; t.Tag:=a; t.OnChange:=TrackBarChange; //événement pour calculer la matrice si l'utilisateur change un angle t.Parent:=GroupBox1; t.Tag:=Random(11)-5; //vitesse de déplacement pour le mode animation end; GroupBox1.ClientHeight:=t.Height+t.Top+3; //on ajuste la hauteur de la groupbox SetLength(FVertexes,(FDim-1)*FDim*PowerOfTwo(FDim-1)); //cette formule donne le nombre de faces pour l'hypercube (multiplié par 4 car une face a 4 sommets) c:=0; n:=PowerOfTwo(FDim-2); for a:=1 to FDim-1 do //on va parcourir toutes les directions de plan possibles en dimension n. Un plan est défini par 2 directions orthogonales ici, elles ont pour numéro for b:=a+1 to FDim do //a et b par rapport à la base canonique MakeSubFaces; //on crée pour la direction du plan, tous les carrés possibles en prenant toutes les combinaisons de 1 et de -1 pour les coordonnées de numéro différent de a et b MakeMatrix; //on actualise la matrice de rotations Caption:='Hypercube: '+IntToStr((High(FVertexes)+1) div 4)+' faces'; //nombre total de faces end; procedure TForm1.TrackBarChange(Sender: TObject); //cet événement est appelé lorsqu'une des trackbars créées automatiquement est modifiée begin MakeMatrix; //si l'utilisateur a changé une des trackbars, on recalcule la matrice de rotation. end; function MultMatrix(m,n:TMatrix):TMatrix; //multiplication de 2 matrices var a,b,c:Integer; s:Single; begin for a:=1 to MaxDim do for b:=1 to MaxDim do begin s:=0; for c:=1 to MaxDim do s:=s+m[a,c]*n[c,b]; Result[a,b]:=s; end; end; function GetIdentity:TMatrix; //la matrice identité var a:Integer; begin ZeroMemory(@Result,SizeOf(Result)); for a:=1 to MaxDim do Result[a,a]:=1; end; function MakeRotation(ID,IDD:Integer;Angle:Single):TMatrix; //calcule la matrice de rotation dans le plan défini par les directions canoniques de numéros ID et IDD begin Result:=GetIdentity; Result[ID,ID]:=Cos(Angle); Result[IDD,IDD]:=Result[ID,ID]; Result[IDD,ID]:=Sin(Angle); Result[ID,IDD]:=-Result[IDD,ID]; end; procedure TForm1.MakeMatrix; var a:Integer; begin if Tag0 then //cette condition est utile pour le mode animation, pour ne pas recalculer à chaque fois la matrice Exit; FMatrix:=GetIdentity; FMatrix:=MultMatrix(FMatrix,MakeRotation(2,3,TTrac kBar(GroupBox1.Controls[0]).Position*2*pi/3000)); //les 3 premières rotations sont dans l'espace usuel à 3 dimensions FMatrix:=MultMatrix(FMatrix,MakeRotation(3,1,TTrac kBar(GroupBox1.Controls[1]).Position*2*pi/3000)); FMatrix:=MultMatrix(FMatrix,MakeRotation(1,2,TTrac kBar(GroupBox1.Controls[2]).Position*2*pi/3000)); for a:=0 to 3*(FDim-3)-1 do FMatrix:=MultMatrix(FMatrix,MakeRotation((a mod 3)+1,(a div 3)+4,TTrackBar(GroupBox1.Controls[a+3]).Position*2*pi/3000)); //les suivantes sont dans tous les plans qui contiennent un vecteur de l'espace usuel et un vecteur dans son orthogonal end; function MultVect(m:TMatrix;v:TVect):TVect; //multiplication d'un vecteur par une matrice var a,b:Integer; s:Single; begin for a:=1 to MaxDim do begin s:=0; for b:=1 to MaxDim do s:=s+m[a,b]*v[b]; Result[a]:=s; end; end; function CrossP(u,v:TVect):TVect; //produit vectoriel en dimension 3 (les autres coordonnées sont ignorées) begin Result[1]:=u[2]*v[3]-u[3]*v[2]; Result[2]:=u[3]*v[1]-u[1]*v[3]; Result[3]:=u[1]*v[2]-u[2]*v[1]; end; function Minus(u,v:TVect):TVect; //différence de 2 vecteurs var a:Integer; begin for a:=1 to MaxDim do Result[a]:=u[a]-v[a]; end; procedure TForm1.DrawScene; //la procédure qui trace l'hypercube var a:Integer; procedure DrawVertex(v:TVect); //pour dessiner un vecteur begin //ici, on fait une projection implicite: seule les 3 premières coordonnées du vecteur sont utilisées, ça correspond à projeter orthogonalement glVertex3f(v[1],v[2],v[3]); //l'espace de dimension n sur l'espace usuel de dimension 3 end; procedure DrawFace(Id:Integer;Mode:Cardinal); //pour dessiner une face à 4 côtés en fonction d'un mode de primitive opengl (par exemple GL_QUADS ou GL_LINE_LOOP) var t,u,v,w,n:TVect; begin t:=MultVect(FMatrix,FVertexes[Id]); //on calcule la projection des 4 sommets de la face en utilisant la matrice de rotations u:=MultVect(FMatrix,FVertexes[Id+1]); v:=MultVect(FMatrix,FVertexes[Id+2]); w:=MultVect(FMatrix,FVertexes[Id+3]); n:=Crossp(Minus(t,v),Minus(u,w)); //on calcule le produit vectoriel pour avoir la normale glBegin(Mode); glNormal3f(n[1],n[2],n[3]); //la normale... DrawVertex(t); //... et les 4 coordonnées DrawVertex(u); DrawVertex(v); DrawVertex(w); glEnd; end; procedure DrawFaces(Mode:Cardinal); //pour dessiner toutes les faces de l'hypercube en fonction d'un mode de primitive unique var a:Integer; begin for a:=0 to ((High(FVertexes)+1) div 4)-1 do DrawFace(4*a,Mode); end; const Specular:array[0..3] of GlFloat=(0.8,0.3,0.1,1.0); begin if CheckBox1.Checked then //si on est en mode animation, on fait varier les angles de rotation Animate; glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); //on efface la scène d'avant glViewport(0,0,ClientWidth-Panel1.Width,ClientHeight); //viewport en fonction de la zone de tracé glMatrixMode(GL_PROJECTION); //calcul de la matrice de projection Opengl glLoadIdentity; gluPerspective(100,(ClientWidth-Panel1.Width)/ClientHeight,0.001,10); //calcul de la perspective, avec un angle de 100° pour la caméra gluLookAt(0,0,3,0,0,0,0,1,0); //position de la caméra glColor3f(1,1,1); //couleur par défaut glEnable(GL_NORMALIZE); //renormalisation automatique des vecteurs normaux glPushAttrib(GL_ALL_ATTRIB_BITS); //pour éviter d'avoir a réinitialiser les paramètres par défaut glEnable(GL_LINE_SMOOTH); //plus joli glEnable(GL_POLYGON_SMOOTH); //plus joli case RadioGroup1.ItemIndex of //choix du mode de tracé 0:DrawFaces(GL_LINE_LOOP); //lignes simples 1:begin //faces éclairées glColor3f(0.4,0.6,0.5); //couleur de base glEnable(GL_DEPTH_TEST); //on doit utiliser le Z-buffer pour la gestion des faces cachées glEnable(GL_LIGHTING); //on active la gestion de la lumière glEnable(GL_LIGHT0); //on active la première lampe glEnable(GL_COLOR_MATERIAL); //le matériau est assujeti à la couleur courante glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE); //la couleur courante va modifier la diffusion uniquement glMaterial(GL_FRONT_AND_BACK,GL_SHININESS,5); //pour faire des reflets avec la lampe (donne un aspect plus "métallique") glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,@Specul ar); //couleur spéculaire du matériau glLightModel(GL_LIGHT_MODEL_TWO_SIDE,1); //les faces doivent être éclairées des 2 côtés car on n'a pas gèré l'orientation de la normale DrawFaces(GL_QUADS); //on dessine les faces pleines end; 2:begin //lignes cachées glDepthMask(GL_FALSE); //Z-buffer en lecture seule glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); //color buffer en lecture seule glEnable(GL_STENCIL_TEST); //activation du test de stencil glClear(GL_STENCIL_BUFFER_BIT); //on vide le buffer de stencil glStencilFunc(GL_ALWAYS,1,1); //on va mettre ddes 1 partout où l'on trace glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); DrawFaces(GL_LINE_LOOP); //on trace les lignes glPolygonOffset(0.8,0.8); //permet d'éviter des effets gênants de crênelage glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_DEPTH_TEST); //on active le test de profondeur glDepthMask(GL_TRUE); //mode écriture pour le Z-Buffer glStencilFunc(GL_EQUAL,1,1); //on ne tracera que là où le stencil vaut 1, c'est à dire où on a tracé les arêtes précédemment glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); //on ne modifie pas le stencil DrawFaces(GL_QUADS); //on trace les faces pleines glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); //color buffer en mode écriture DrawFaces(GL_LINE_LOOP); //cette fois on trace les lignes pour de vrai, le test de profondeur empèchera de tracer les morceaux de lignes recouvertes par des faces. end; 3:begin glLineWidth(3); //lignes de largeur 3 glDepthMask(GL_FALSE); //idem que précédemment: le stencil vaut 1 partout où on trace les lignes glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); glEnable(GL_STENCIL_TEST); glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_ALWAYS,1,2); glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); DrawFaces(GL_LINE_LOOP); glPolygonOffset(0.8,0.8); //idem que précédemment: on a mis à jour le Z-buffer partout où il y a des faces glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); glStencilFunc(GL_EQUAL,1,2); glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); DrawFaces(GL_QUADS); glColor3f(1,0.5,0); //couleur rouge-orangé glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); //idem que précédemment, on trace des lignes larges partout où le test de profondeur le permet... glStencilOp(GL_KEEP,GL_KEEP,GL_INCR); //...mais cette fois on stocke 2 dans le stencil partout où les lignes sont visibles DrawFaces(GL_LINE_LOOP); glColor3f(0.4,0.6,0.5); //on trace les polygones avec la lumière comme précédemment... glDepthFunc(GL_LEQUAL); glStencilFunc(GL_NOTEQUAL,2,2); //...mais cette fois si uniquement où le stencil ne vaut pas 2, c'est à dire où il n'y a pas de lignes visibles glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE); glMaterial(GL_FRONT_AND_BACK,GL_SHININESS,5); glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,@Specul ar); glLightModel(GL_LIGHT_MODEL_TWO_SIDE,1); DrawFaces(GL_QUADS); glLineWidth(1); //cette fois on va tracer les lignes cachées (fines et avec de l'alpha) glEnable(GL_BLEND); glColor4F(1,0.5,1,0.5); //couleur bleu-violet avec transparence glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); glStencilFunc(GL_EQUAL,1,2); //on aura le droit de tracer uniquement là où le stencil vaut 1 glStencilOp(GL_KEEP,GL_KEEP,GL_INCR); //et on modifie le stencil pour que les lignes ne puissent pas se recouvrir 2 fois glDisable(GL_LIGHTING); // glEnable(GL_LINE_STIPPLE); glLineStipple(1,$1); DrawFaces(GL_LINE_LOOP); end; 4:begin glLineWidth(3); glDepthMask(GL_FALSE); glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); glEnable(GL_STENCIL_TEST); glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_ALWAYS,1,1); glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); DrawFaces(GL_LINE_LOOP); glPolygonOffset(0.8,0.8); glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); glStencilFunc(GL_EQUAL,1,1); glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); DrawFaces(GL_QUADS); glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); DrawFaces(GL_LINE_LOOP); //jusque là c'est semblable au mode avec les lignes cachées glDisable(GL_DEPTH_TEST); glClear(GL_STENCIL_BUFFER_BIT); glEnable(GL_STENCIL_TEST); for a:=0 to ((High(FVertexes)+1) div 4)-1 do begin //cette fois on trace les lignes cachées en pointillé glLineWidth(1); //largeur des lignes cachées: 1 glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); glStencilFunc(GL_EQUAL,0,1); glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); glEnable(GL_LINE_STIPPLE); glLineStipple(1,$1); //motif en pointillé DrawFace(4*a,GL_LINE_LOOP); glLineWidth(3); //cette étape interdit de tracer les lignes qui se recouvrent 2 fois glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); glDisable(GL_LINE_STIPPLE); glStencilFunc(GL_ALWAYS,1,1); glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE); DrawFace(4*a,GL_LINE_LOOP); end; end; end; glPopAttrib; end; procedure TForm1.Animate; var a,b:Integer; begin Tag:=1; //pour ne pas recalculer la matrice à chaque fois for a:=0 to GroupBox1.ControlCount-1 do //pour toutes les trackbars créées, on fait varier leur position en fonction de la vitesse de déplacement stockée dans Tag with TTrackBar(GroupBox1.Controls[a]) do begin b:=Position+Tag; if bMax then Position:=Min+(b-Max) else Position:=b; end; end; Tag:=0; MakeMatrix; //on calcule la matrice à la fin, en une seule fois end; procedure TForm1.SpeedButton1Click(Sender: TObject); begin ComboBox1Change(nil); //reset des angles end; function FloatToStr2(x:Single):string; //cette fonction transforme un nombre en une chaine dans un format utilisable par POV-Ray begin Str(x,Result); end; procedure TForm1.SpeedButton2Click(Sender: TObject); //on écrit le source pour POV-Ray dans le répertoire du programme var f:TextFile; a,n:Integer; v:TVect; begin AssignFile(f,ExtractFilePath(APplication.ExeName)+ 'Vertexes.inc'); //on ouvre le fichier... ReWrite(f); //...en mode écriture try n:=(High(FVertexes)+1) div 4; WriteLn(f,'#declare FacesCount='+IntToStr(n)+';'); WriteLn(f,''); WriteLn(f,'#declare Vertexes=array['+IntToStr(4*n)+'][3]'); WriteLn(f,'{'); for a:=0 to High(FVertexes) do begin //on écrit toutes les coordonnées des sommets dans le fichier... v:=MultVect(FMatrix,FVertexes[a]); if a Descargar la aplicación Autor Suscribirse : Weiterlesen... |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |