1795 - Macierzowe Przekształcenia Przestrzenne

Jeśli chcesz kogoś zadziwić (zainteresować, zaintrygować), to na pewno pokażesz napisany przez siebie program. Jeśli jednak chcesz wprawić kogoś w kompletne osłupienie, program ów powinien zawierać animację czegoś, i to najlepiej w przestrzeni. Jeśli ponadto włożysz w ten program trochę serca i dobrych pomysłów, powstanie dzieło, które rzuci na kolana każdego, kto nań spojrzy, dzieło, które może być namiastką tak ostatnio modnej rzeczywistości wirtualnej. A jak to zrobić ?

W powszechnie dostępnej w naszym kraju literaturze ( np. [1] ) znajdujemy pewne rozwiązania tego problemu - chyba jednak niezbyt szczęśliwe. Nawet w najlepszej, moim zdaniem, pozycji na polskim rynku, jaką jest [2], nie wszystko zostało jasno przedstawione. Nie wchodząc w szczegóły ww. prac można postawić tezę, że przekształcenia brył w przestrzeni to zagadnienie bardzo proste, wręcz elementarne, jeśli zostanie w tym celu użyta macierzowa reprezentacja punktu i - co za tym idzie - całej bryły.

Obroty bryły wokół dowolnej osi, skalowanie, czyli zmiana rozmiaru, translacja (przesunięcie) oraz eliminacja niewidocznych krawędzi, bo o te elementy transformacji przestrzennej chodzi, najprościej i najskuteczniej jest wykonać, przyjmując następujące założenia :

  • Punkt P będzie macierzą o wymiarach 4 x 1 (cztery rzędy i jedna kolumna) o wartości, jak na Rys.1.
  • Bryła będzie tablicą (lub listą) punktów (zmienna "figura1" w przykładzie), które w każdym kroku transformacji przestrzennej będą podlegały iloczynowi macierzy P x B (pętla "for" w części głównej programu), gdzie B to macierz złożenia przekształceń o wymiarach 4 x 4 (o tym za moment).
  • Na ekranie zostanie wyświetlony rzut bryły na jedną z płaszczyzn (rzut na płaszczyznę XY wymaga jedynie pominięcia współrzędnej Z podczas kreślenia krawędzi - procedura "krawedz").
  • Jeśli krawędzi niewidocznych z pozycji obserwatora nie należy rysować lub na którejś ze ścian ma się coś pojawić, trzeba jeszcze tylko sprawdzić, które ściany są w tym momencie niewidoczne - krawędź łącząca dwie niewidoczne ściany jest również niewidoczna. Jeśli zaś chodzi o widoczność ścian, wyznacza się ją na podstawie iloczynu wektorów (Rys.1), za które  przyjmuje się krawędzie badanych ścian - zwrot tych wektorów należy ustalić zgodnie z ruchem wskazówek zegara, gdy patrzy się na ścianę z zewnątrz bryły, prostopadle do niej (Rys.2). Gdy wynik iloczynu wektorów, który oczywiście też będzie wektorem, jest zwrócony do obserwatora (zwrot zgodny z regułą śruby prawoskrętnej, czyli wielkość Z dodatnia), oznacza to, że ściana jest niewidoczna - w przeciwnym wypadku jest widoczna.

Macierzą złożenia przekształceń B może być któraś z macierzy przekształceń przestrzennych lub iloczyn tych macierzy. Przedstawia je Rys.1, gdzie:

  • S to macierz skalowania, czyli zmiany rozmiaru - pozwala symulować zbliżanie się lub oddalanie obiektu czy zmianę odległości od obserwatora,
  • T to macierz translacji, czyli przesunięcia,
  • Rx, Ry i Rz - macierze obrotu wokół odpowiednich osi.

Złożenia tych przekształceń wystarczą, aby uzyskać na ekranie wspaniałe i zadziwiające efekty wizualne.

 
Rys.1Rys.2

Literatura :

  • [1] A. Marciniak "Turbo Pascal 5.5"
  • [2] Ian 0. Angell "Wprowadzenie do grafiki komputerowej"

Andrzej Postrzednik - PC Kurier 17/1995r.


 { ********************************* }
 { * MACIERZOWE PRZEKSZTAŁCENIA 3D * }
 { * Demo - obrót czworościanu     * }
 { ********************************* }

 Program P3D; Uses Graph, Crt;

 Type Macierz = Array [1..4, 1..4] Of Real;

 Var T, Rx, Ry, Rz, S, A, B : Macierz;
     Figura1 : Array [0..3] Of Macierz;
     Rys1 : Array [0..3] Of Macierz;
     Ekr, K : Integer;

 { Mnożenie Macierzy C = A * B }

 Procedure Iloczyn_Macierzy(M,N,P: Integer;
                            A,B : Macierz;
                            Var C : Macierz);
 Var I,J,K : Integer;
     Suma : Real;
 Begin
   For I := 1 To M Do
     For J := 1 To P Do
     Begin
       Suma := 0;
       For K := 1 To N Do
         Suma := Suma + A[I,K] * B[K,J];
       C[I,J] := Suma;
     End;
   {}
 End;

 { Inicjalizacja Macierzy Przekształceń }
 Procedure Ustal_Macierz_T(Tx,Ty,Tx: Integer);
 Var I,J : Integer;
 Begin
   For I := 1 To 4 Do
   Begin
     For J := 1 To 4 Do T[I,J] := 0;
     T[I,I] := 1;
   End;
   T[1,4] := -Tx;
   T[2,4] := -Ty;
   T[3,4] := -Tz;
 End;

 Procedure Ustal_Macierz_S(Sx,Sy,Sz : Real);
 Var I,J : Integer;
 Begin
   For I := 1 To 4 Do
   Begin
     For J := 1 To 4 Do S[I,J] := 0;
     S[I,I} := 1;
   End;
   S[1,1] := Sx;
   S[2,2] := Sy;
   S[3,3] := Sz;
 End;

 Procedure Ustal_Macierz_Rx(Alfa: Real);
 Var I,J : Integer;
     Kat : Real;
 Begin
   For I := 1 To 4 Do
   Begin
     For J := 1 To 4 Do Rx[I,J] := 0;
     Rx[I,I] := 1;
   End;
   Kat := Alfa * Pi/180;
   Rx[2,2] := Cos(Kat);
   Rx[3,3] := Rx[2,2]
   Rx[2,3] := Sin(Kat);
   Rx[3,2] := Rx[2,3];
 End;

 Procedure Ustal_Macierz_Ry(Alfa: Real);
 Var I,J : Integer;
     Kat : Real;
 Begin
   For I := 1 To 4 Do
   Begin
     For J := 1 To 4 Do Ry[I,J] := 0;
     Ry[I,I] := 1;
   End;
   Kat := Alfa * Pi/180;
   Ry[1,1] := Cos(Kat);
   Ry[3,3] := Ry[1,1];
   Ry[3,1] := Sin(Kat);
   Ry[1,3] := Ry[3,1];
 End;

 Procedure Ustal_Macierz_Rz(Alfa: Real);
 Var I,J : Integer;
     Kat : Real;
 Begin
   For I := 1 To 4 Do
   Begin
     For J := 1 To 4 Do Rz[I,J] := 0;
     Rz[I,I] := 1;
   End;
   Kat := Alfa * Pi/180;
   Rz[1,1] := Cos(Kat);
   Rz[2,2] := Rz[1,1];
   Rz[1,2] := Sin(Kat);
   Rz[2,1] := Rz[1,2];
 End;

 { Rysuje zrzutowany na płaszczyznę XY odcinek }

 Procedure Krawedz(Var Fig3D: Array Of Macierz;
                   N1,N2 : Byte; X,Y : Integer);

 Var X1,Y1,X2,Y2 : Integer;

 Begin
   X1 := Round(Fig3D[N1][1,1]);
   Y1 := Round(Fig3D[N1][2,1]);
   X2 := Round(Fig3D[N2][1,1]);
   Y2 := Round(Fig3D[N2][2,1]);
   Line(X1 + X, Y1 + Y, X2 + X, Y2 + Y);
 End;

 { Określa widoczność ściany na podstawie   }
 { numerów wierzchołków liczonych prawoskrętnie }

 Procedure Widocznosc(Fig3D: Array Of Macierz;
                      P1,P2,P3 : Byte): Boolean;
 Var I : Integer;
     Ax,Bx,Ay,By : Real;
 Begin
   Ax := Fig3D[P2][1,1] - Fig3D[P1][1,1];
   Ay := Fig3D[P2][2,1] - Fig3D[P1][2,1];
   Bx := Fig3D[P3][1,1] - Fig3D[P2][1,1];
   By := Fig3d[P3][2,1] - Fig3D[P2][2,1];
   If Ax * Ay < 0 Then Widocznosc := True
   Else Widocznosc := False;
 End;

 { Ogólna procedura inicjalizująca } 
 { wsp. przestrzenne }

 Procedure Ustal_Wspolrzedne(Var Fig3D: 
            Array Of Macierz; N: Byte;
            X,Y,Z : Real);
 Begin
   Fig3D[N][1,1] := X;
   Fig3D[N][2,1] := Y;
   Fig3D[N][3,1] := Z;
   Fig3D[N][4,1] := 1;
 End;

 { Konkretne współrzędne początkowe dla }
 { bryły FIGURA1 }

 Procedure Ustal_Figure1;
 Begin
   Ustal_Wspolrzedne(Figura1, 0, 50, -30, -20);
   Ustal_Wspolrzedne(Figura1, 1, -50, -30, -20);
   Ustal_Wspolrzedne(Figura1, 2, 0, 30, -20);
   Ustal_Wspolrzedne(Figua1, 3, 0, 0, 40);
 End;

 { Konkretne współrzędne początkowe dla rysunku }
 { na jednej ze ścian }

 Procedure Ustal_Rys1;
 Begin
   Ustal_Wspolrzedne(Rys1, 0, 10, 10, -20);
   Ustal_Wspolrzedne(Rys1, 1, -10, 10, -20);
   Ustal_Wspolrzedne(Rys1, 2, -10, -10, -20);
   Ustal_Wspolrzedne)Rys1, 3, 10, -10, -20);
 End;

 Procedure Wyswietl_Rys1(X,Y: Integer);
 Begin
   Krawedz(Rys1, 0, 1, X, Y);
   Krawedz(Rys1, 1, 2, X, Y);
   Krawedz(Rys1, 2, 3, X, Y);
   Krawedz(Rys1, 3, 0, X, Y);
 End;

 Procedure Wyświetl_Figure1(X,Y: Integer);
 Var I,J : Integer;
     Sc1,Sc2,Sc3,Sc4 : Boolean;
 Begin

   {* Określenie widoczności ściany *}

   Sc1 := Wodocznosc(Figura1, 0, 2, 1);
   Sc2 := Widocznosc(Figura1, 0, 3, 2);
   Sc3 := Widocznosc(Figura1, 1, 3, 0);
   Sc4 := Widocznosc(Figura1, 3, 1, 2);
   
   { Wyświetlenie ilustracji na widocznych }
   { scianach }
   
   If Sc1 Then Wyswietl_Rys1(X,Y);
   
   { Krawędzie pomiędzy poszczególnymi }
   { ścianami }

   If Sc1 Or Sc2 Then Krawedz(Figura1, 0, 2, X,Y);
   If Sc1 Or Sc3 Then Krawedz(Figura1, 0, 1, X,Y);
   If Sc1 Or Sc4 Then Krawedz(Figura1, 1, 2, X,Y);
   If Sc2 Or Sc3 Then Krawedz(Figura1, 0, 3, X,Y);
   If Sc3 Or Sc4 Then Krawedz(Figura1, 1, 3, X,Y);
   If Sc2 Or Sc4 Then Krawedz(Figura1, 2, 3, X,Y);
 End;

 Procedure OpenGraph;
 Var Karta,Tryb : Integer;
 Begin
   Karta := VGA;
   Tryb := VGAMed;
   InitGraph(Karta,Tryb,'C:\TP\Bgi');
 End;

 { *** Część Główna Programu *** }

 Begin
   Ustal_Figure1;
   Ustal_Rys1;
   Ustal_Macierz_T(1,0,0);
   Ustal_Macierz_s(1.01, 1.01. 1.01);
   Ustal_Macierz_Rz(-5);
   Ustal_Macierz_Ry(10);
   Ustal_Macierz_Rx(6);
   Iloczyn_Macierzy(4 ,4, 4, Ry, Rx, B);
   Iloczyn_Macierzy(4, 4, 4, B, S, B);
   Iloczyn_Macierzy(4, 4, 4, B, T, B);
   Ekr := 0;
   OpenGraph;
   Repeat
     { wymiana ekranów }
     SetVisualPage(Ekr Mod 2);
     SetActivePage((Ekr + 1) Mod 2);
     Inc(Ekr);
     ClearDevice;
     Wyswietl_Figure1(420,310);
     Delay(10);
     { ** Przekształcaj Bryłę ** }
     For K := 0 To 3 Do
     Begin
       Iloczyn_Macierzy(4,4,1,B,Figura1[K],A);
       Figura[K] := A;
     End;
     { ** Przekształcaj Rysunek Na Ścianie ** }
     For K := 0 To 3 Do
     Begin
       Iloczyn_Macierzy(4,4,1,B,Rys1[K],A);
       Rys1[K] := A;
     End;
   Until KeyPressed;
   CloseGraph;
 End.