Grafika wektorowa przy wykorzystaniu obiektów spod "3D STUDIO"

    -- Sebastian Pawlak, 1998.




Pobierz plik wektory.zip

Dość prostym do zaprogramowania efektem jest grafika wektorowa, obrazowana przez poruszające się w przestrzeni punkty (vector dots) lub inne obiekty, np. kule (vector balls). Algorytm animujący wektorową bryłę nie jest złożony, ogranicza się on do wyliczania, przekształconych o kąt, pozycji punktów w przestrzeni i odwzorowywaniu ich na płaskim ekranie monitora. Najwięcej problemów może sprawić definicja trójwymiarowej bryły. Ręczne opisywanie w przestrzeni, wszystkich punktów nie ma sensu, jest to czynność nużąca i nie wygodna, zwłaszcza gdy chcemy zdefiniować wiele wierzchołków. Najlepiej posłużyć się jakimś edytorem, który zapisze do pliku pozycje X, Y i Z wszystkich punktów bryły. Z pomocą przychodzi tu znane wszystkim "3D STUDIO". W wygodnym edytorze tworzymy dowolny obiekt i zapisujemy go do pliku w formacie ASC (w oknie zapisu do pliku kliknąć na przycisk z napisem *.ASC). Przykładowy plik z zapisanym sześcianem będzie wyglądał tak:

Ambient light color: Red=0.039216 Green=0.039216 Blue=0.039216
Named object: "Object01"
Tri-mesh, Vertices: 8     Faces: 12
Vertex list:
Vertex 0:  X: -50.749714     Y: -50.672333     Z: -50.16436
Vertex 1:  X: 50.749714     Y: -50.672333     Z: -50.16436
Vertex 2:  X: 50.749714     Y: -50.672325     Z: 51.335068
Vertex 3:  X: -50.749714     Y: -50.672325     Z: 51.335068
Vertex 4:  X: -50.749714     Y: 50.591961     Z: -50.164371
Vertex 5:  X: 50.749714     Y: 50.591961     Z: -50.164371
Vertex 6:  X: 50.749714     Y: 50.591969     Z: 51.335056
Vertex 7:  X: -50.749714     Y: 50.591969     Z: 51.335056
Face list:
Face 0:	A:0 B:1 C:2 AB:1 BC:1 CA:0
Smoothing:  1
Face 1:	A:0 B:2 C:3 AB:0 BC:1 CA:1
Smoothing:  1
Face 2:	A:0 B:4 C:5 AB:1 BC:1 CA:0
Smoothing:  2
Face 3:	A:0 B:5 C:1 AB:0 BC:1 CA:1
Smoothing:  2
Face 4:	A:1 B:5 C:6 AB:1 BC:1 CA:0
Smoothing:  3
Face 5:	A:1 B:6 C:2 AB:0 BC:1 CA:1
Smoothing:  3
Face 6:	A:2 B:6 C:7 AB:1 BC:1 CA:0
Smoothing:  4
Face 7:	A:2 B:7 C:3 AB:0 BC:1 CA:1
Smoothing:  4
Face 8:	A:3 B:7 C:4 AB:1 BC:1 CA:0
Smoothing:  5
Face 9:	A:3 B:4 C:0 AB:0 BC:1 CA:1
Smoothing:  5
Face 10:		A:4 B:7 C:6 AB:1 BC:1 CA:0
Smoothing:  6
Face 11:		A:4 B:6 C:5 AB:0 BC:1 CA:1
Smoothing:  6

Nas interesują pozycje wierzchołków zdefiniowanych kolejno jako Vertex 0, Vertex 1, itd. Dalej zapisane są informacje o płaszczyznach, nie będą one przydatne w programie, który za chwilę przedstawię, ale omówię zasadę ich definiowania przez 3D STUDIO. Każdy obiekt składa się ze ścian (płaszczyzn), sześcian ma ich 6. Ze względu na wygodę nakładania materiału na bryły, 3D STUDIO rozbija wszystkie płaszczyzny na trójkąty, i tak jeden kwadratowy bok sześcianu składa się z dwóch trójkątów (ich linia wspólna poprowadzona jest po przekątnej kwadratu) co daje w sumie 12 płaszczyzn na ten obiekt. Wszystkie trójkąty (płaszczyzny) składają się z 3 linii co zapisane jest w pliku ASC jako "Face".

Face 0:	A:0 B:1 C:2 AB:1 BC:1 CA:0

Na początek zmiennym A, B, C przypisywane są numery wierzchołków (są to numery według Vertex list), w tym wypadku kolejno 0, 1 i 2. Następnie znajduje się informacja, które wierzchołki połączone są ze sobą - wierzchołek A z B (wierzchołek 0 z 1), B z C (1 z 2) i C z A (2 z 0). Po każdym dwuliterowym określeniu połączeń znajduje się cyfra 0 lub 1, co odpowiada 0 -linia niewidoczna (np. linia wspólna dwóch trójkątów wchodzących w skład większej płaszczyzny), 1- linia widoczna (np. krawędź bryły). Informacje o płaszczyznach są przydatne przy pisaniu programu animującego bryły, w których między wierzchołkami kreślone są linie (line vectors) lub nakładana jest tekstura. Teraz pora na prezentację programu animującego bryłę (vector dots):

USES CRT;
CONST Distance=400; {Odległość bryły od obserwatora}

TYPE Punkt3D= record
 X,Y,Z: integer;
END;

VAR Xangle,Yangle,Zangle:integer;
    IloscWierzcholkow:word;
    _Cos,_Sin:array [0..359] of real;
    ObiektWzor,ObiektObrot:array [0..500] of Punkt3D;
    i,j:integer;

{Procedura stawiająca punkt o współrzędnych X, Y i danym}
{kolorze}
PROCEDURE PutPixel (X,Y:word;Kolor:byte); ASSEMBLER;
ASM
 PUSH ES
 MOV AX,$a000
 MOV ES,AX
 MOV AX,X
 MOV BX,Y
 MOV CL,6
 SHL BX,CL {mnoży wspł. Y przez 2 do potęgi 6, czyli 64}
 MOV CX,BX {wynik mnożenia zapisuje w CX}
 SHL BX,1  {BX mnoży przez 2}
 SHL BX,1  {i jeszcze przez 2 ; razem przez 4}
 ADD BX,CX {sumuje 64*4 i 64 co daje 320 (razy Y)}
 ADD BX,AX {do 320*Y dodaje X}
 MOV DL,Kolor
 MOV ES:[BX],DL
 POP ES
END;

{Procedura przełączająca tryby graficzne}
PROCEDURE InitGraph (Tryb:byte); ASSEMBLER;
ASM
 MOV AH,00H
 MOV AL,Tryb
 INT 10H
END;

{Procedura czekająca na powrót pionowy}
PROCEDURE GraphWait; ASSEMBLER;
ASM
 MOV DX,03daH
@CZEKAJ:
 IN AL,DX
 TEST AL,8
 JE @CZEKAJ
END;

{Funkcja odszukująca w łańcuchu znaków zadany tekst}
FUNCTION FindText (S,Text:string;StartPosition:integer):
                                                 integer;
VAR i,j:integer;
    OdnalezioneZnaki:byte;
    MiejsceZnalezienia:integer;

BEGIN
 i:=StartPosition-1;
 MiejsceZnalezienia:=-1;

 IF Length (S)-Length (Text)>=0 THEN
 BEGIN
  REPEAT
   Inc (i);
   OdnalezioneZnaki:=0;
   FOR j:=0 TO Length (Text)-1 DO
    IF S [i+j]=Text [j+1] THEN Inc (OdnalezioneZnaki);
   IF OdnalezioneZnaki=Length (Text) THEN
                               MiejsceZnalezienia:=i;
  UNTIL (i=Length (S)-Length (Text))
        OR(MiejsceZnalezienia<>-1);
 END;
 FindText:=MiejsceZnalezienia;
END;

{Funkcja zamieniająca znajdującą się, na podanej pozycji w}
{łańcuchu znaków, liczbę na odpowiadającą typu Integer}
FUNCTION GetNumber (S:string;StartPosition:integer):integer;
VAR i,j:integer;
    NumberText:string;
    Number:real;
    Code:word;

BEGIN
 {Szuka miejsca, gdzie kończy się zapis liczby}
 j:=FindText (S,' ',StartPosition)-1;
 {Jeśli nie znalazł końca liczby oznacza to, że jest nim}
 {koniec wiersza}
 IF j=-2 THEN j:=Length (s);

 NumberText:='';
 FOR i:=StartPosition To j DO NumberText:=NumberText+S [i];
 Val (NumberText,Number,Code);
 GetNumber:=Round (Number);
END;

{Procedura wczytująca do pamięci kształt bryły 3D z pliku}
{zapisanego w formacie ASC spod "3D STUDIO"}
PROCEDURE LoadVectorObject;
VAR i:integer;
    F:text;
    S:string;

BEGIN
 IF ParamStr (1)='' THEN
 BEGIN
  WriteLn ('Punktowa animacja bryły zapisanej w *.ASC');
  WriteLn ('parametr: Vector.EXE plik.ASC');
  WriteLn;
  Write ('plik.ASC - plik w formacie ASC, spod ');
  WriteLn ('"3D Studio", z definicja');
  WriteLn ('           obracanego obiektu.');
  Halt;
 END;

 Assign (F,ParamStr (1));
 {$I-}
 Reset (F);
 IF IOResult<>0 THEN
 BEGIN
  Writeln ('Blad otwarcia pliku !');
  Halt;
 END;
 {$I+}

 REPEAT    {Szukanie, w pliku, sygnatury VERTICES}
  ReadLn (F,S);
  i:=FindText (S,'Vertices: ',1);
 UNTIL (i<>-1)OR(EOF (F));

 {Odczytuje ile wierzchołków ma bryła}
 IloscWierzcholkow:=GetNumber (S,i+Length ('Vertices: '));

 ReadLn (F,S); {Jeden wiersz w dół}

 i:=0;
 {Odczyt pozycji X, Y i Z poszczególnych wierzchołków}
 REPEAT
  ReadLn (F,S);
  IF FindText (S,'Vertex',1)<>-1 THEN
  BEGIN
   ObiektWzor [i].x:=
         GetNumber (S,FindText (s,'X: ',1)+length ('X: '));
   ObiektWzor [i].y:=
         GetNumber (S,FindText (s,'Y: ',1)+length ('Y: '));
   ObiektWzor [i].z:=
         GetNumber (S,FindText (s,'Z: ',1)+length ('Z: '));
   Inc (i);
  END;
 UNTIL i=IloscWierzcholkow;
 Close (F);
END;

{Procedura wyliczająca pozycję wierzchołków}
{przekształconych o kąt i wyświetlająca bryłe na ekranie}
PROCEDURE Draw3Dobject;
VAR Nx,Ny,Nz:integer;
    PunktBlizszy:word;

BEGIN
 FOR i:=0 TO IloscWierzcholkow-1 DO
 BEGIN
  {Wymazanie starego punktu lub kulki z ekranu}
  PutPixel (ObiektObrot [i].X,ObiektObrot [i].Y,0);

  {Obrót względem osi X}
  Ny:=Round (ObiektWzor [i].Y*_Cos [Xangle]
      -ObiektWzor [i].Z*_Sin [Xangle]);
  Nz:=Round (ObiektWzor [i].Y*_Sin [Xangle]
      +ObiektWzor [i].Z*_Cos [Xangle]);
  Nx:=Round (ObiektWzor [i].X);
  ObiektObrot [i].X:=Nx;
  ObiektObrot [i].Y:=Ny;
  ObiektObrot [i].Z:=Nz;

  {Obrót względem osi Y}
  Nx:=Round (ObiektObrot [i].X*_Cos [Yangle]
      -ObiektObrot [i].Z*_Sin[Yangle]);
  Nz:=Round (ObiektObrot [i].X*_Sin [Yangle]
      +ObiektObrot [i].Z*_Cos [Yangle]);
  ObiektObrot [i].X:=Nx;
  ObiektObrot [i].Z:=Nz;

  {Obrót względem osi Z}
  Nx:=Round (ObiektObrot [i].X*_Cos [Zangle]
      -ObiektObrot [i].Y*_Sin [Zangle]);
  Ny:=Round (ObiektObrot [i].X*_Sin [Zangle]
      +ObiektObrot [i].Y*_Cos [Zangle]);
  ObiektObrot [i].X:=Nx;
  ObiektObrot [i].Y:=Ny;

  {Perspektywa}
  Dec (ObiektObrot [i].Z,Distance);
  ObiektObrot [i].X:=Round ((ObiektObrot [i].X*256)
                     /ObiektObrot [i].Z+160);
  ObiektObrot [i].Y:=Round ((ObiektObrot [i].Y*256)
                     /ObiektObrot [i].Z+100);
  Inc (ObiektObrot [i].Z,Distance);
 END;

 {Wyświetlanie punktów.}
 {Ponieważ punkty reprezentowane są poprzez pojedyncze}
 {piksle, przed ich wyświetleniem nie trzeba przeprowadzać}
 {sortowania pod względem odległości od ekranu.}
 FOR i:=0 To IloscWierzcholkow-1 DO
 PutPixel (ObiektObrot [i].X,ObiektObrot [i].Y,15);
END;

BEGIN
 LoadVectorObject;

 InitGraph ($13);

 FOR i:=0 TO 359 DO
 BEGIN
  _Sin [i]:=Cos (i*PI/180);
  _Cos [i]:=Sin (i*PI/180);
 END;

 {Główna pętla programu}
 REPEAT
  Inc (Xangle);
  Inc (Yangle);
  Inc (Zangle);

  IF Xangle>359 THEN Xangle:=Xangle-360;
  IF Yangle>359 THEN Yangle:=Yangle-360;
  IF Zangle>359 THEN Zangle:=Zangle-360;

  graphwait;
  Draw3Dobject;
 UNTIL keypressed;

 InitGraph ($3);
END.

Animacja bryły 3D byłaby bardziej efektowna gdyby piksle reprezentujące wierzchołki zastąpić kulkami - dodatkowo kulki powinny zmieniać swój rozmiar w zależności od odległości od obserwatora. Ponieważ implementacja takiego efektu jest dłuższa niż program animujący za pomocą piksli, toteż nie zamieszczam jej tu. Osoby zainteresowane mogą znaleźć źródła w pliku z wydrukami z tego numeru PCQ.


Literatura: 1. "Efekty graficzne w asemblerze"
               - Michał Gawrylczyk
w3cw3c
automatyka przemysłowa