sześcian 3d delphi

0

Witam! mam problem z programem zaliczeniowym.
Mam do zrobienia sześcian obracający się we wszystkich osiach, przesuwający się i odbijający od ścian.
Znalazłem macierze obrotów i przesunięcia i rzutowania ale nie wiem jak mam dalej ruszyć i nie wiem co mam zrobić z tymi odbiciami...
bardzo proszę o pomoc!</ort>

0

Ogólnie polecam Ci rysowanie przez DirectX albo OpenGL, ale jak potrzebujesz to mieć zrobione ze standardowym VCL, to taki kod naskrobałem na szybko. Odbijanie od ścian zrób sobie sam, ja nie mam w tej chwili czasu ani chęci. Za błędy nie odpowiadam :]

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Buttons, ExtCtrls, StdCtrls, Math;

type

    Matrix=array[0..2,0..2] of real;
    TVertex=record
      x:real;
      y:real;
      z:real;
    end;

  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Button1: TButton;
    Timer1: TTimer;
    ScrollBar1: TScrollBar;
    ScrollBar2: TScrollBar;
    ScrollBar3: TScrollBar;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ScrollBar1Change(Sender: TObject);
    procedure ScrollBar2Change(Sender: TObject);
    procedure ScrollBar3Change(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  var
  Form1: TForm1;

implementation

{$R *.DFM}

{ILOŚĆ WIERZCHOŁKÓW}
const
  ALL_POINTS = 8;

var
  bbuffer:TBitmap;
  dbuffer:TBitmap;
  PntsOut:  array[0..ALL_POINTS-1] of TVertex;
  TPPnts:   array[0..ALL_POINTS-1] of TPoint;
  Pnts:     array[0..ALL_POINTS-1] of TVertex;
  Xang,Yang,Zang:real;

{OBLICZENIA MATEMATYCZNE}

procedure MatrixRotate(var M:Matrix; X,Y,Z:real);
var
   sinX,cosX,
   sinY,cosY,
   sinZ,cosZ:real;
begin
sinX:=sin(X);
sinY:=sin(Y);
sinZ:=sin(Z);
cosX:=cos(X);
cosY:=cos(Y);
cosZ:=cos(Z);

M[0,0]:=(cosZ*cosY);
M[0,1]:=(cosZ*sinY*sinX)+(sinZ*cosX);
M[0,2]:=(sinZ*sinX)-(cosZ*sinY*cosX);
M[1,0]:=-(sinZ*cosY);
M[1,1]:=(cosZ*cosX)-(sinZ*sinY*sinX);
M[1,2]:=(sinZ*sinY*cosX)+(cosZ*sinX);
M[2,0]:=(sinY);
M[2,1]:=-(cosY*sinX);
M[2,2]:=(cosY*cosX);
end;

procedure RotatePoint(PointIn:TVertex; var PointOut:TVertex; M:Matrix);
begin
PointOut.x:=(PointIn.x*M[0,0])+(PointIn.y*M[0,1])+(PointIn.z*M[0,2])+1;
PointOut.y:=(PointIn.x*M[1,0])+(PointIn.y*M[1,1])+(PointIn.z*M[1,2])+1;
PointOut.z:=(PointIn.x*M[2,0])+(PointIn.y*M[2,1])+(PointIn.z*M[2,2])+1;
end;

function CreatePoint(X,Y,Z:integer):TVertex;
begin
CreatePoint.x:=X;
CreatePoint.y:=-Y;
CreatePoint.z:=Z;
end;

procedure initCube();
var i:word;
begin
for i:=0 to (ALL_POINTS div 2)-1 do
  begin
  Pnts[i] := CreatePoint( round(sin(2*PI*i/(ALL_POINTS div 2))*50),  round(cos(2*PI*i/(ALL_POINTS div 2))*50), -50 );
  Pnts[i+ALL_POINTS div 2] := CreatePoint( round(sin(2*PI*i/(ALL_POINTS div 2))*50),  round(cos(2*PI*i/(ALL_POINTS div 2))*50), 50 );
  end;
end;

procedure showLine(v1,v2:integer; sidecolor:Tcolor);
begin
dbuffer.canvas.pen.color:=sidecolor;
dbuffer.canvas.moveto(TPPnts[v1 mod ALL_POINTS].x,TPPnts[v1 mod ALL_POINTS].y);
dbuffer.canvas.lineto(TPPnts[v2 mod ALL_POINTS].x,TPPnts[v2 mod ALL_POINTS].y);
end;

{ZDARZENIA W APLIKACJI}

procedure TForm1.FormCreate(Sender: TObject);
begin
dbuffer:=Tbitmap.create;
dbuffer.Height:=400;
dbuffer.Width:=400;
bbuffer:=TBitmap.create;
bbuffer.Height:=400;
bbuffer.Width:=400;
bbuffer.canvas.brush.color:=clWhite;
bbuffer.canvas.rectangle(0,0,400,400);
initCube();
xang:=0;
yang:=0;
zang:=0;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
bbuffer.free;
dbuffer.free;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var m:matrix;
    cnt:integer;
begin
dbuffer.canvas.copyrect(Rect(0,0,400,400),bbuffer.canvas,Rect(0,0,400,400));
xang:=xang+ScrollBar1.Position;
yang:=yang+ScrollBar2.Position;
zang:=zang+ScrollBar3.Position;
MatrixRotate(M,(PI*xang)/180,(PI*yang)/180,(PI*zang)/180);
for cnt:=0 to ALL_POINTS-1 do
  begin
  RotatePoint(Pnts[cnt],PntsOut[cnt],M);
  TPPnts[cnt].X:=round(185*PntsOut[cnt].x / (PntsOut[cnt].z+185.2)+120);
  TPPnts[cnt].y:=round(185*PntsOut[cnt].y / (PntsOut[cnt].z+185.2)+120);
  TPPnts[cnt]:=Point(trunc(PntsOut[cnt].x+200),trunc(PntsOut[cnt].y+200));
  end;
for cnt:=0 to (ALL_POINTS div 2)-1 do
  begin
  showline(cnt,cnt+(ALL_POINTS div 2), RGB(cnt*64,cnt*64,0));
  showline(cnt+(ALL_POINTS div 2),(cnt+1) mod (ALL_POINTS div 2)+(ALL_POINTS div 2), RGB(cnt*64,cnt*64,0));
  showline(cnt,(cnt+1) mod (ALL_POINTS div 2), RGB(cnt*64,cnt*64,0));
  end;
PaintBox1.Canvas.CopyRect(Rect(0,0,400,400),dbuffer.canvas,Rect(0,0,400,400));
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Scrollbar1.position:=0;
Scrollbar2.position:=0;
Scrollbar3.position:=0;
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
label4.caption:=inttostr(scrollbar1.position);
end;

procedure TForm1.ScrollBar2Change(Sender: TObject);
begin
label5.caption:=inttostr(scrollbar2.position);
end;

procedure TForm1.ScrollBar3Change(Sender: TObject);
begin
label6.caption:=inttostr(scrollbar3.position);
end;

end.
0

Wielkie dzięki Szczawik, powiedz mi tylko czemu to nie działa ? :)
wrzucam kod do Delphi i spluwa się o elementy :/
proszę o dalszą pomoc...
z góry przepraszam za banalne być może dla kogoś problemy, dopiero raczkuję w temacie grafiki w delphi.
Czytałem wszelkie tutoriale, ale nie daję sobie z tym rady...
jeszcze raz z góry dzięki za pomoc!

Edit: już sobie poradziłem :)
troszkę zmodyfikowałem kod pod swoje potrzeby, ale wielkie dzięki jeszcze raz!
teraz tylko proszę o pomysł na odbijanie go od ścian...

0

Po pierwsze listing komponentów masz w kodzie, więc z tym nie powinno być problemu! Po drugie - zrób coś samemu, a odbijanie jest tu najłatwiejszą rzeczą.

0

Z jakiegoś powodu nie powiodło się wysłanie maila. Masz tu pełen kod źródłowy. Jeden wczytuje texture, drugi jest jako główny program. Uwaga: w procedurze glInit masz linie

LoadTexture('olek.bmp', CubeTex);

Zrób sobie najprostrzy obrazek o rozmiarach 256x256 i zamiast tej nazwy wpisz nazwe twojego.

Na formie umieść jeden Panel.

UNIT texture;

{*****************************************************************************

*  Author:      Szymon Tengler
*  E-mail:      [email protected]
*  Last modify: 25.05.2005

{*****************************************************************************}

INTERFACE
USES
    SysUtils, OpenGL, Windows;
TYPE
	//Data OF the image
    TImageData = ARRAY OF byte;

//import odpowiednich procedur z opengl32.dll
PROCEDURE glGenTextures(n: GLsizei; VAR textures: GLuint); STDCALL; EXTERNAL opengl32;
PROCEDURE glBindTexture(target: GLenum; texture: GLuint); STDCALL; EXTERNAL opengl32;
//procedura ladujaca teksture
PROCEDURE LoadTexture( filename: PChar;  VAR texture: GLuint);

IMPLEMENTATION

//
//funkcja ladujaca bitmape
//
//------------------------------------------------------------------------------
FUNCTION LoadBMP(FileName: PChar; VAR biFH: BITMAPINFOHEADER): TImageData;
VAR
    bFH: BITMAPFILEHEADER;
    fileHandle: integer;
    i: Cardinal;
    tempRGB: byte;
CONST BMPTYPE = 19778;
BEGIN
    IF NOT FileExists(Filename) THEN //jezeli plik nie istnieje
    BEGIN
      Result := NIL;
      exit;
    END;

    fileHandle := FileOpen(FileName, fmOpenRead);

    FileRead (fileHandle, bFH, sizeof(BITMAPFILEHEADER) );

    IF bFH.bfType <> BMPTYPE THEN
    BEGIN
        FileClose(fileHandle);
        Result := NIL;
        exit;
    END;

    FileRead(fileHandle, biFH, sizeof(BITMAPINFOHEADER) );

    FileSeek(fileHandle, bFH.bfOffBits, 0); //

    SetLength(result, biFH.biSizeImage);

    FOR i:=0 TO biFH.biSizeImage-1 DO
        FileRead(fileHandle, result[i], 1);

    //BGR TO RGB
    i := 0;
    WHILE (i < biFH.biSizeImage) DO
    BEGIN
        tempRGB := result[i];
        result[i] := result[i + 2];
        result[i + 2] := tempRGB;
        Inc(i, 3);
    END;

    FileClose(fileHandle);
END;
//------------------------------------------------------------------------------

//
//funkcja ladujaca teksture
//
PROCEDURE LoadTexture( filename: PChar;  VAR texture: GLuint);
VAR
    image: BITMAPINFOHEADER;
    buffor: TImageData;
BEGIN
    //zaladowanie bitmapy
    buffor := LoadBMP(filename, image);

    IF (Assigned(buffor)) THEN //jezeli zaladowanie bitmapy sie powiodlo
    BEGIN
        //ustawinie tekstury i skojazenie jej z bitmapa
        glGenTextures( 1, texture );
        glBindTexture( GL_TEXTURE_2D, texture );

        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        //glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
        glTexImage2D( GL_TEXTURE_2D, 0, 4, image.biWidth, image.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, buffor );
    END
    ELSE
        MessageBox( HWND(NIL), PAnsiChar('Nie udalo się załadować tekstury o nazwie: ' + filename + #10#13 +'Upewnij się czy podana jej lokalizacja jest prawidłowa' ), 'Informacja', MB_OK);
END;
//------------------------------------------------------------------------------

END.

UNIT UNIT1;

INTERFACE

USES
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  OpenGl, Texture, ExtCtrls, StdCtrls;

TYPE
  TForm1 = CLASS(TForm)
    Panel1: TPanel; { animacja                       }
    Timer1: TTimer; { generowanie zdarzenia glDraw() }
    PROCEDURE FormCreate(Sender: TObject);
    PROCEDURE FormDestroy(Sender: TObject);
    PROCEDURE FormResize(Sender: TObject);
    PROCEDURE Timer1Timer(Sender: TObject);
  PRIVATE
    { Private declarations }
    rc : HGLRC;    // Rendering Context
    dc : HDC;      // Device Context
    PROCEDURE glInit; { inicjacja OpenGla }
    PROCEDURE glDraw; { rysowanie sceny   }
  PUBLIC
    { Public declarations }
  END;

VAR
  Form1   : TForm1;
  CubeTex : gluInt;   { dla tekstury }
  rot     : glFloat; { dla rotacji  }

TYPE
 { wspolzedne punktu w 3D      }
 { x,y,z - wspolzedne punktu   }
 { u,v   - wspolzedne tekstury }
 TPoint = RECORD  x, y, z, u, v :glFloat;  END;

CONST
 { definicja kostki szesciennej }
 Cube  :ARRAY [0..5, 0..3] OF TPoint = (
  // przednia sciana
  ((x:-1.0; y:-1.0; z: 1.0; u: 0.0; v: 0.0),
   (x: 1.0; y:-1.0; z: 1.0; u: 1.0; v: 0.0),
   (x: 1.0; y: 1.0; z: 1.0; u: 1.0; v: 1.0),
   (x:-1.0; y: 1.0; z: 1.0; u: 0.0; v: 1.0)),
  // tylna sciana
  ((x:-1.0; y:-1.0; z:-1.0; u: 1.0; v: 0.0),
   (x:-1.0; y: 1.0; z:-1.0; u: 1.0; v: 1.0),
   (x: 1.0; y: 1.0; z:-1.0; u: 0.0; v: 1.0),
   (x: 1.0; y:-1.0; z:-1.0; u: 0.0; v: 0.0)),
  // gorna sciana
  ((x:-1.0; y: 1.0; z:-1.0; u: 0.0; v: 1.0),
   (x:-1.0; y: 1.0; z: 1.0; u: 0.0; v: 0.0),
   (x: 1.0; y: 1.0; z: 1.0; u: 1.0; v: 0.0),
   (x: 1.0; y: 1.0; z:-1.0; u: 1.0; v: 1.0)),
  // dolna sciana
  ((x:-1.0; y:-1.0; z:-1.0; u: 1.0; v: 1.0),
   (x: 1.0; y:-1.0; z:-1.0; u: 0.0; v: 1.0),
   (x: 1.0; y:-1.0; z: 1.0; u: 0.0; v: 0.0),
   (x:-1.0; y:-1.0; z: 1.0; u: 1.0; v: 0.0)),
  // prawa sciana
  ((x: 1.0; y:-1.0; z:-1.0; u: 1.0; v: 0.0),
   (x: 1.0; y: 1.0; z:-1.0; u: 1.0; v: 1.0),
   (x: 1.0; y: 1.0; z: 1.0; u: 0.0; v: 1.0),
   (x: 1.0; y:-1.0; z: 1.0; u: 0.0; v: 0.0)),
  // lewa sciana
  ((x:-1.0; y:-1.0; z:-1.0; u: 0.0; v: 0.0),
   (x:-1.0; y:-1.0; z: 1.0; u: 1.0; v: 0.0),
   (x:-1.0; y: 1.0; z: 1.0; u: 1.0; v: 1.0),
   (x:-1.0; y: 1.0; z:-1.0; u: 0.0; v: 1.0)));

IMPLEMENTATION

PROCEDURE TForm1.glInit;
BEGIN
  glClearColor(0.0, 0.0, 0.0, 0.0); 	   // Czarne tlo
  glShadeModel(GL_SMOOTH);                 // Gladkie cieniowanie
  glClearDepth(1.0);                       // Ustawienie bufora glebi
  glEnable(GL_DEPTH_TEST);                 // Wlacz bufor
  glDepthFunc(GL_LESS);		           // Typ glebi
  // korekcja perspektywy
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  glEnable(GL_TEXTURE_2D);                 // wlacz teksturowanie
  LoadTexture('olek.bmp', CubeTex);   // zaladuj teksture
END; { TForm1.Init }

PROCEDURE TForm1.glDraw;
VAR i, j :glByte;
BEGIN
 glClear(GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT); // czysc ekran i glebie
 glLoadIdentity();                                    // laduj scene
 glTranslatef(0.0, 0.0, -5.0);                        // przesun w glab
 glRotatef(rot, 1.0, 1.0, 1.0);                       // obrot
 glBindTexture(GL_TEXTURE_2D, CubeTex);               // domyslna tekstura
 glBegin(GL_QUADS);                                   // rysuj kwadrat
   FOR i := 0 TO 5 DO
    FOR j := 0 TO 3 DO
     WITH Cube[i, j] DO
      BEGIN
       glTexCoord2f(u, v); glVertex3f(x, y, z);
      END;
 glEnd();
 rot := rot+3;
END; { TForm1.Draw }

{$R *.DFM}

PROCEDURE TForm1.FormCreate(Sender: TObject);
VAR pfd : TPIXELFORMATDESCRIPTOR;
    pf  : Integer;
BEGIN
  // gdzie chcemy rysowac
  dc:=GetDC(Panel1.Handle);
  // PixelFormat
  pfd.nSize:=sizeof(pfd);
  pfd.nVersion:=1;
  pfd.dwFlags:=PFD_DRAW_TO_WINDOW OR PFD_SUPPORT_OPENGL OR PFD_DOUBLEBUFFER OR 0;
  pfd.iPixelType:=PFD_TYPE_RGBA;      // PFD_TYPE_RGBA OR PFD_TYPEINDEX
  pfd.cColorBits:=16;

  pf :=ChoosePixelFormat(dc, @pfd);   // Returns format that most closely matches above pixel format
  SetPixelFormat(dc, pf, @pfd);

  rc :=wglCreateContext(dc);    // Rendering Context = window-glCreateContext
  wglMakeCurrent(dc,rc);        // Make the DC (Form1) the rendering Context

  // Initialist GL environment variables
  glInit();
END; {TForm1.FormCreate }

PROCEDURE TForm1.FormDestroy(Sender: TObject);
BEGIN
  wglMakeCurrent(Panel1.Handle,0);
  wglDeleteContext(rc);
END;

PROCEDURE TForm1.FormResize(Sender: TObject);
BEGIN
  glViewport(0, 0, Panel1.Width, Panel1.Height);    // SET the viewport FOR the OpenGL window
  glMatrixMode(GL_PROJECTION);        // Change Matrix Mode TO Projection
  glLoadIdentity();                   // Reset View
  gluPerspective(45.0, Panel1.Width/Panel1.Height, 1.0, 500.0);  // DO the perspective calculations. Last value = max clipping depth
  glMatrixMode(GL_MODELVIEW);         // Return TO the modelview matrix
END;

PROCEDURE TForm1.Timer1Timer(Sender: TObject);
BEGIN
 glDraw();
 SwapBuffers(DC);
END;

END.
0
Szczawik napisał(a)

Po drugie - zrób coś samemu, a odbijanie jest tu najłatwiejszą rzeczą.

Odbijanie kanciastej bryły jest prostą rzeczą (zwłaszcza głową :-D ). :d

Jeśli zaniedbać rotacje to będzie proste, tylko że wtedy mamy punkt zamiast sześcianu. [???]

0

Po pierwsze listing komponentów masz w kodzie, więc z tym nie powinno być problemu! Po drugie - zrób coś samemu, a odbijanie jest tu najłatwiejszą rzeczą.

Szczawik nie bulwersuj się...Prosiłem o hinty nie o kody źródłowe, ale tak czy inaczej dziękuję :)
Inną rzeczą że pytanie na tym forum było ostatecznością, bo siedziałem nad tym i modziłem. Tak więc nie zarzucaj mi że nic nie robię...
Pomijając, mam pytanie do Ciebie jeszcze odnośnie Twojego programu.
Mianowicie jest stała zdefiniowana na początku ALL_POINTS . Bawiłem się podstawianiem różnych wartości i wychodziły mi ciekawe figury, nie wiem jednak jak zmodyfikować kod, żebym w programie mógł podstawiać za ALL_POINTS własne wartości.Próbowałem różnych opcji ale do niczego nie doszedłem...jakiś mały hint ?
Zmieniając ALL_POINTS na zmienną globalną spluwa mi się że potrzebuje (oczywiste) stałej.

Oleksy Adam wielkie dzięki za świetny kod, jednak jak na razie to dla mnie wyższa szkoła jazdy :)

0

Nie bulwersowałem się - jak tak zabrzmiało, to przepraszam. Kod jest celowo napisany, by tworzył graniastosłupy prawidłowe. ALL_POINTS musi być stałą, bo tablice są statyczne:

PntsOut:  array[0..ALL_POINTS-1] of TVertex;
TPPnts:   array[0..ALL_POINTS-1] of TPoint;
Pnts:     array[0..ALL_POINTS-1] of TVertex;

Jeśli chcesz mieć możliwość modyfikowania tego przez program, używaj tablic dynamicznych. Oprócz tej zmiany, nie powinno być większych problemów.

0

no oczywiście...walnąłem się w łeb że prawie mi zęby powybijało :) wielkie dzięki za pomoc...rozkminiam jeszcze te odbicia, i kiepsko mi idzie, ale jeszcze chce sam nad tym posiedzieć :)

0

Witam, to znowu ja, Adam. Co do twoich nieszczęsnych odbić. Teoretycznie aby odbijanie było poprawne (i w miarę proste) należy wkomponować sześcian w kulę o promieniu 0.5 przekądnej sześcianu. Tu wstawię fragment kodu odpowiedzialnego za odbijanie kuli ziemskiej od krawędzi ekranu (kula, sześcian, co za różnica) i tak operujemy współżędnymi punktu środka. Jeżeli masz Turbo lub Borland Pascala to tu masz mój adres email: [email protected]. Daj znać i email prześlę źródła (ok. 6000 wierszy to trochę dużo do publikacji)

{ px i py to współżędne punktu, natomiast dx i dy to wektory przesunięć }
       IF (px+dx < 0) OR (px+dx > 260) THEN     { sprawdzam lewa, potem prawa granica ekranu }
          BEGIN
           dx := -dx; { jeżeli tak to zmień kierunek ruchu na przeciwny }
          END;
       IF (py+dy < 0) OR (py+dy > 140) THEN     { sprawdzam górna, potem dolna granica ekranu }
          BEGIN
           dy := -dy;
          END;
       Inc(px, dx);
       Inc(py, dy);
0

mam pytanko. ten pierwszy programik dziala mi ok. a ten drugi skladajacy sie z dwoch unitow sie oko kompiluje i odpala ale pojawia sie tylko czarny panel i nic wiecej. co robier zle? prosze bardzo o pomoc...

0

Pojęcia nie mam dlaczego masz czarny panel. Może zależy to od wersji kompilatora lub OpenGl. Ja ma delphi 7 i OpenGl 2.0. Jak chcesz to masz mój adres: [email protected]. Napisz to prześlę źródła razem z exe.

0

U mnie dziala ok. Moze cos z tekstura masz nie tak?

0

:-O jesli mozecie poprosze o wyslanie mi zrodelek najlepiej z plikiem exe na maila [email protected] moze cos mam zle.. mam delphi 7 ale nie wiem jaka wersje opengl.. dziekuje z gory!!

0

Nie napisalem ze bardzo dziekuje za pomoc [!!!] bardzo skorzystalem. pozdrawiam!! :-)

1 użytkowników online, w tym zalogowanych: 0, gości: 1