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