Morphing w Turbo Pascalu

Oleksy_Adam

Morfing (skrót od angielskiego Morphing). Skr. od ang. metamorphosing (przekształcanie). W grafice komputerowej: animacja realizująca płynną zmianę jednego obrazu graficznego w inny.

Niniejszą definicją zaczynam artykuł poświecony implementacji w/w efektu w uwaga: w Turbo Pascalu . Wiem, że język ten jest już archaiczny, jednak jest to nadal Pascal, czyli wiele informacji można wykorzystać w Delphi. Poniżej znajduje się lista, zagadnień, które wykorzystanie są w programie.
? Opis silnika graficznego wykorzystanego przy pisaniu programu
? Wykorzystanie wstawek assemblera w kodzie programu
? Przerwania i porty ? użycie
? Podejście obiektowe i dziedziczenie
? Sposoby odwzorowania trzywymiarowości na ekranie monitora
? Optymalizacja kodu nastawiona na wydajność

Wiadomym, jest fakt, że grafiki BGI (Borland Graphic Interface) nie można użyć przy generowaniu grafiki 3d z transformacjami ze względu na jej szybkość. Na programiście spoczywa ciężar napisania procedur zajmujących się wyświetlaniem prymitywów graficznych, oraz implementacja szeregu zagadnień, takich jak podwójne buforowanie, programowanie palety kolorów itp. Nie jest to jednak zagadnienie, bardzo złożone.

Efekt morpingu został zrealizowany w oparciu o rozproszone piksele, przybierające na ekranie różne formy, od chaotycznej po figury geometryczne, takie jak półkula czy sześcian.

Na samym wstępie należy ustalić z ilu punktów będą się składać owe obiekty. Trudność polega na tym, że z tej samej ilości punktów należy zbudować różne bryły. Na drodze eksperymentu doszedłem do następującej deklaracji:

CONST
 MaxPkt = 215;    { liczba punktow z ktorych budujemy figury }

Kolejnym elementem jest stworzenie struktury, która pozwoli na reprezentację piksela w 3D. Standardowy pascalowy TPoint jest dobry dla 2D. Na nasze potrzeby jest on niewystarczający, ponieważ nie ma w nim osi Z. Deklaracja typu wygląda następująco:

TYPE
 TPoint = RECORD X,Y,Z :Real; END;    { rekord na punkt 3d }

W przypadku symulacji zjawisk fizycznych owy rekord uległby powiększeniu o pola takie jak: wektory prędkości, energię cząstki, kolor, itp. Na nasze potrzeby wystarczy powyższa deklaracja.

Następnie musimy utworzyć tablicę, która będzie zdolna do przechowywania danego obiektu.

TYPE
 TTab = ARRAY [0..MaxPkt] OF TPoint;  { tablica na budowe bryly }

Początek za nami. Teraz należy się zastanowić nad strukturą samego programu. Począwszy od wersji 5.5 dostępne jest podejście obiektowe, które w tym przypadku jest jak najbardziej odpowiednie. Obiektem bazowym będzie Tbryła. Klasa ta zawierać będzie szkielet każdego obiekty 3D. Nie jest ważne jaki kształt ma obiekt, tak więc, różnice będą tylko w konstruktorach. Cała reszta procedur związanych z obrotami, transformacjami i wyświetlaniem jest uniwersalna.
Deklaracja klasy Tbryła jest następująca:

TYPE
 { Obiekt podstawowy. Tworzy on bryle pusta. }
 { Procedury dziedzicza przodkowie.          }
 PBryla = ^TBryla;
 TBryla = OBJECT
  { inicjujemy obiekt }
  CONSTRUCTOR Init(VAR Tab :TTab);
  { Obrot w przestrzeni o podany kat. Wartosc zwracana przez refernecje }
  PROCEDURE Rotate(VAR Tab :TTab; kx,ky,kz :Integer); VIRTUAL;
  { Morphing obiektu.       }
  { ATab - tablica zrodlowa }
  { BTab - tablica docelowa }
  { CTab - tablica wynikowa }
  PROCEDURE Morph(ATab, BTab :TTab; VAR CTab :TTab); VIRTUAL;
  { Obrot obiektu o ILE stopni }
  PROCEDURE RotateMorph(VAR Tab :TTab; Ile :Word); VIRTUAL;
  { Wyswietla obiekt na ekranie }
  PROCEDURE Draw(Tab :TTab); VIRTUAL;
  { Konczymy zabawe }
  DESTRUCTOR Done; VIRTUAL;
 END;

Definicje poszczególnych metod obiektu:

Wyzerowanie tablicy przechowującej współrzędne. Można to zrealizować za pomocą pętli for, jednak powyższa konstrukcja jest krótsza, prostsza, bardziej elegancka i nie musimy deklarować dodatkowej zmiennej.

CONSTRUCTOR TBryla.Init(VAR Tab :TTab);
BEGIN
 FillChar(Tab, SizeOf(TTab), 0);
END; { TBryla.Init }

Idąc w kolejności, następną metodą do zaimplementowania jest Rotate. Jej ciało znajduje się poniżej (kx, ky, kz to kąty obrotu obiektu). Dyrektywa warunkowa

{$IFDEF Delay}

ma na celu ustalenie prędkości animacji w zależności od prędkości komputera. Im szybszy procesor tym opóźnienie animacji musi być większe.

PROCEDURE TBryla.Rotate(VAR Tab :TTab; kx,ky,kz :Integer);
VAR TX, TY, TZ :Real;
BEGIN
{$IFDEF Delay}
 Delay(Pause);
{$ENDIF}
 FOR wi := 0 TO MaxPkt DO
  BEGIN
    { obrot wokol osi X }
    TZ := Tab[wi].Y*TabSin[kx] + Tab[wi].Z*TabCos[kx];
    TY := Tab[wi].Y*TabCos[kx] - Tab[wi].Z*TabSin[kx];
    Tab[wi].Y := TY;
    Tab[wi].Z := TZ;
    { obrot wokol osi Y }
    TX := Tab[wi].Z*TabSin[ky] + Tab[wi].X*TabCos[ky];
    TZ := Tab[wi].Z*TabCos[ky] - Tab[wi].X*TabSin[ky];
    Tab[wi].X := TX;
    Tab[wi].Z := TZ;
    { obrot wokol osi Z }
    TX := Tab[wi].Y*TabSin[kz] + Tab[wi].X*TabCos[kz];
    TY := Tab[wi].Y*TabCos[kz] - Tab[wi].X*TabSin[kz];
    Tab[wi].X := TX;
    Tab[wi].Y := TY;
  END;
END;

W owej metodzie pojawiają się stałe TabSin i TabCos. Ich deklaracja wygląda następująco:

VAR
 TabSin : ARRAY[0..359] OF real;
 TabCos : ARRAY[0..359] OF real;

Zabieg ten ma na celu optymalizację szybkości działania programu. Obliczanie funkcji trygonometrycznych jest jedną z najbardziej czasochłonnych rzeczy dla procesora. Na obliczenie sinusa potrzeba około 39 cykli procesora, gdzie na dodawanie lub odejmowanie potrzeba dwóch cykli. Inicjacja tablic trygonometrycznych realizowana jest przez funkcję

PROCEDURE InitSinCos;
VAR i : word;
BEGIN
 FOR i := 0 TO 359 DO
  BEGIN
   TabSin[i] := sin((PI*i)/180);
   TabCos[i] := cos((PI*i)/180);
  END; {koniec petli for}
END;

Która zawarta jest w module odpowiedzialnym za 3D. Wartości kątowe są w stopniach, a nie w radianach, stąd we wzorze PI i 180.

Przejdźmy teraz do tytułowego problemu. Implementacji efektu morphingu. Nie jest to zagadnienie złożone aczkolwiek wymagało trochę przemyślenia. Idea jest taka, że potrzebne są trzy obiekty: źródłowy, obiekt wynikowy, obiekt aktualnie transformowany. Obiekt źródłowy i wynikowy są nieruchome. Obrotowi ulega obiekt wynikowy. Działanie metody jest proste:
Jeżeli Obiekt.A.X < Obiekt.B.X to Obiekt.A.X++. (inkrementuj)
Jeżeli Obiekt.A.X > Obiekt.B.X to Obiekt.A.X--. (dekrementuj)

Pętla jest zakańczana w momencie zrównania wszystkich współrzędnych czyli
Jeżeli Obiekt.A.X = Obiekt.B.X to Koniec.

Wewnątrz tej procedury odbywa się także obracanie i wyświetlanie na ekranie. Kod metody:

PROCEDURE TBryla.Morph(ATab, BTab :TTab; VAR CTab :TTab);
VAR Stop :Boolean;
BEGIN
  rx := 0;
  ry := 0;
  rz := 0;
 REPEAT
  { nie bardzo wiadomo jakie wartosci przyjmuja wspolzedne punktow   }
  { tak wiec zastosowalem dwa warunki przerwania petli:              }
  { 1). nacisniecie klawisza                                         }
  { 2). Osiagniecie przez zmienna Stop wartosci TRUE                 }
  { Zaokraglenie wartosci jest konieczne gdyz wspolzedne zwiekszamy  }
  { o 1 tak wiec warunek zakonczenia petli nigdy nie bylby spelniony }
  CTab := ATab; { konieczne bo morphujemy nierchoma tablice          }
  Stop := TRUE;
{$IFDEF Delay}
  Delay(Pause);
{$ENDIF}
  IF KeyPressed THEN Break;
  FOR wi := 0 TO MaxPkt DO
   BEGIN
    IF Round(ATab[wi].X) > Round(BTab[wi].X) THEN
       BEGIN Stop := FALSE;  ATab[wi].X := ATab[wi].X-1; END;
    IF Round(ATab[wi].X) < Round(BTab[wi].X) THEN
       BEGIN Stop := FALSE;  ATab[wi].X := ATab[wi].X+1; END;
    IF Round(ATab[wi].Y) > Round(BTab[wi].Y) THEN
       BEGIN Stop := FALSE;  ATab[wi].Y := ATab[wi].Y-1; END;
    IF Round(ATab[wi].Y) < Round(BTab[wi].Y) THEN
       BEGIN Stop := FALSE;  ATab[wi].Y := ATab[wi].Y+1; END;
    IF Round(ATab[wi].Z) > Round(BTab[wi].Z) THEN
       BEGIN Stop := FALSE;  ATab[wi].Z := ATab[wi].Z-1; END;
    IF Round(ATab[wi].Z) < Round(BTab[wi].Z) THEN
       BEGIN Stop := FALSE;  ATab[wi].Z := ATab[wi].Z+1; END;
   END;
   Inc(rx, 1);
   IF (rx > +359) THEN Dec(rx, 360);
   IF (rx < -359) THEN Inc(rx, 360);
   Inc(ry, 1);
   IF (ry > +359) THEN Dec(ry, 360);
   IF (ry < -359) THEN Inc(ry, 360);
   Inc(rz, 1);
   IF (rz >  359) THEN Dec(rz, 360);
   IF (rz < -359) THEN Inc(rz, 360);
  Rotate(CTab, rx, ry, rz);
  Draw(CTab);
 UNTIL Stop;
END;

Wcześniej zaimplementowaliśmy procedurę do obrotu pojedynczego punktu, teraz musimy zrobić ją dla całej tablicy czyli w pętli for.

PROCEDURE TBryla.RotateMorph(VAR Tab :TTab; Ile :Word);
VAR T :TTab;
BEGIN
 rx := 0;  ry := 0;  rz := 0;
 FOR wj := 0 TO Ile DO
  BEGIN
   IF KeyPressed THEN Break;
   T := Tab;
   Inc(rx, 1);
   IF (rx > +359) THEN Dec(rx, 360);
   IF (rx < -359) THEN Inc(rx, 360);
   Inc(ry, 1);
   IF (ry > +359) THEN Dec(ry, 360);
   IF (ry < -359) THEN Inc(ry, 360);
   Inc(rz, 1);
   IF (rz >  359) THEN Dec(rz, 360);
   IF (rz < -359) THEN Inc(rz, 360);
   Rotate(T, rx,ry,rz);
   Draw(T);
  END;
 Tab := T;
END; 

W powyższych dwóch procedurach pojawia się linia:

IF KeyPressed THEN Break;

Znaczy to tyle, że jeżeli user naciśnie jakikolwiek klawisz to animacja zostaje przerwana.

Następnym krokiem jest wyświetlenie obiektów na ekranie. Jest to zadanie proste, aczkolwiek będzie wymagało szerszego omówienia techniki.

PROCEDURE TBryla.Draw(Tab :TTab);
BEGIN
 ClsBuf(Buf, Black);      { czyscimy bufor                   }
 FOR wi := 0 TO MaxPkt DO { rysujemy prostokaciki 2x2        }
  { im wieksza odleglosc punktu od oka tym ciemniejszy kolor }
  PutPixel(Buf, SX+Round(Tab[wi].X), SY-Round(Tab[wi].Y),     
                Byte(Round(Tab[wi].Z))+170);
 { informacje o tworcy programu }
 OutTextXY(Buf, 5,10, '3D Morphing Demo by Olek International', 255);
 MoveMem(Buf, VgaAddr); { kopiowanie bufora na ekran }
 PowrotPionowy;
END;

Pojawiają się w niej procedury:
? ClsBuf
? PutPixel
? OutTextXY
? MoveMem
? PowrótPionowy

Po kolei:
ClsBuf - czyści bufor na którym rysujemy. Może to być ekran lub bufor pomocniczy.

PROCEDURE ClsBuf(Buf :Pointer; Kolor :Byte); ASSEMBLER;
ASM
   LES  DI, [Buf]   { segment do ES offset do DI }
   MOV  CX, 32000 { rozmiar ekranu 32000 * word }
   MOV  AH, [Kolor]
   MOV  AL, AH
   CLD
   REP  STOSW       { zapisz CX razy AX do ES:[DI], Inc(DI, 2) }
END; { ClsBuf }

Zastosowałem tu opcję 16 bitową. Można pokombinować z wersją 32 bity, jednak na nasze potrzeby byłaby to niepotrzebna komplikacja.

PutPixel - punkt

PROCEDURE PutPixel(Buf :Pointer; X, Y :Word; Kolor :Byte); ASSEMBLER;
ASM
 LES  DI, Buf
 MOV  AX, [Y]
 MOV  DI, AX
 SHL  AX, 8
 SHL  DI, 6
 ADD  DI, AX
 ADD  DI, [X]
 MOV  AL, [Kolor]
 MOV  BYTE PTR ES:[DI], AL
END; { PutPixel }

Cieniowanie pikseli jest w wersji uproszczonej, na podstawie współrzędnej Z. Im większa, tym dalej od oka, tym ciemniejszy kolor.

OutTextXY - wyświetlanie tekstu na ekranie. Ze względów objętościowych nie wkleję. Do wglądu w module.

Została nam już tylko implementacja destruktora.

DESTRUCTOR TBryla.Done;
BEGIN
 { bez komentarza }
END;

Destruktor jest jako abstrakcyjny, aczkolwiek Borland zaleca stosowanie destruktora jeżeli użyto konstruktora.

Mając zdefiniowaną klasę bazową możemy implementować poszczególne figury.

TYPE
 PKula = ^TKula;
 TKula = OBJECT (TBRyla)
  CONSTRUCTOR Init(VAR Tab :TTab);
 END;

CONSTRUCTOR TKula.Init(VAR Tab :TTab);
CONST
 lambda :Real = 360/16; { dlugosc geograficzna   }
 fi     :Real = 360/27; { szerokosc geograficzna }
 Rad    :Word = 70;     { promien kuli           }
BEGIN
 TBryla.Init(Tab);
 FOR wi := 0 TO 7 DO
  FOR wj := 0 TO 26 DO
   BEGIN
    Tab[wi*27+wj].X := CosX(wj*lambda) * (Rad*CosX(wi*fi));
    Tab[wi*27+wj].Y := SinX(wj*lambda) * (Rad*CosX(wi*fi));
    Tab[wi*27+wj].Z := SinX(wi*fi) * Rad;{}
   END;
END;

Nie podam implementacji wszystkich konstruktorów, jedynie deklaracje aby nie zaciemniać:

TYPE
 PStozek = ^TStozek;
 TStozek = OBJECT (TBryla)
  CONSTRUCTOR Init(VAR Tab :TTab);
 END;

PSpirala = ^TSpirala;
 TSpirala = OBJECT (TBryla)
  CONSTRUCTOR Init(VAR Tab :TTab);
 END;

PSzescian = ^TSzescian;
 TSzescian = OBJECT (TBryla)
  CONSTRUCTOR Init(VAR Tab :TTab);
 END;

PPlaszczyzna = ^TPlaszczyzna;
 TPlaszczyzna = OBJECT (TBryla)
  CONSTRUCTOR Init(VAR Tab :TTab);
 END;

Tym oto sposobem dochodzimy do głównego programu (ten moment musi kiedyś nastąpić J)

Deklaracja obiektu programu:

TYPE
 TProg = OBJECT
  Roj         :PRoj;           { zmienne obiektowe }
  Kula        :PKula;
  Stozek      :PStozek;
  Spirala     :PSpirala;
  Szescian    :PSzescian;
  Plaszczyzna :PPlaszczyzna;
  Count       :Word;           { licznik dla RotateMorph     }
  CONSTRUCTOR Init;            { inicjujemy zmienne programu }
  PROCEDURE Run;               { glowna petla programu       }
  PROCEDURE Paleta;            { ustawianie palety kolorow   }
  DESTRUCTOR Done;             { zwalniamy pamiec            }
 END;

Konstruktor ma za zadanie czyszczenie ekranu, utworzenie bufora, inicjację zmiennych obiektowych, inicjację trybu graficznego i palety kolorów (odcienie szarości).

CONSTRUCTOR TProg.Init;
BEGIN
 Randomize;                { generator liczb losowych ON }
 SetVgaMode(GraphMode);    { tryb 320x200x256 ON         }
 GetMemBuf(Buf);           { przydzielamy pamiec bufora  }
 ClsBuf(Buf, Black);       { czyscimy bufor              }
 Paleta;                   { ustawiamy palete            }
 Pause := 5;               { spowolnienie animacji       }
 Count := 400;             { licznik RotateMorph         }
 Plaszczyzna := New(PPlaszczyzna, Init(T1));    { inicjujemy zmienne }
 Szescian := New(PSzescian, Init(T1));          { obiektowe          }
 Spirala := New(PSpirala, Init(T1));
 Stozek := New(PStozek, Init(T1));
 Kula := New(PKula, Init(T1));
 Roj := New(PRoj, Init(T1));
END; { TProg.Init }

SetVgaMode gdzie:

CONST
 GraphMode  :Byte    = $13;              { tryb graficzny 320x200x256 }
 NormalMode :Byte    = $03;              { tryb tekstowy 80x25        }

oraz

PROCEDURE SetVgaMode(Mode :Byte); ASSEMBLER;
ASM
 MOV AH, 00h
 MOV AL, [Mode]
 INT 10h
END; { SetVgaMode }

Tryb 13h jest trybem 320x200x256. Łatwość programowania sprawia, że jest on (był) często wykorzystywany przez twórców gier (pamiętamy chyba wszyscy Wolfenstein 3D J). Nie będę rozpisywał się na temat jego właściwości, tylko polecę książki Piotr Besta ?Tworzenie gier w Turbo Pascalu? oraz Andrzej Dudek ?Jak pisać wirusy? (jest to wspaniała podróż w głąb systemu i assemblera).

Następnie należy ustalić bufor tymczasowy w którym odbywać się będzie rysowanie sceny. Dzięki temu unikniemy nieprzyjemnego efektu migania ekranu. Idea jest taka, że obraz tworzony jest w pamięci i w momencie trwania powrotu pionowego monitora (od prawego dolnego rogu ekranu do lewego górnego) kopiujemy zawartość bufora procedurą MoveMem. Ustalenie bufora odbywa się za pomocą: GetMemBuf. Każda scena przed narysowaniem wymaga wyczyszczenia bufora (chyba, że chcemy uzyskać blur) procedurą ClsBuf.Oto kody procedur:

PROCEDURE MoveMem(Zrodlo, Cel :Pointer); ASSEMBLER;
ASM
   PUSH  DS             { kopiowanie 16 bitowe              }
   LDS   SI, [Zrodlo]   { segment do DS offset do SI        }
   LES   DI, [Cel]      { segment do ES offset do DI        }
   MOV   CX, 32000      { liczba powtorzen                  }
   CLD                  { adresy rosnace                    }
   REP   MOVSW          { kopiuj bajty z DS:[SI] do ES:[DI] }
   POP   DS.
END; { MoveMem }

PROCEDURE ClsBuf(Buf :Pointer; Kolor :Byte); ASSEMBLER;
ASM
   LES  DI, [Buf]   { segment do ES offset do DI               }
   MOV  CX, 32000
   MOV  AH, [Kolor]
   MOV  AL, AH
   CLD
   REP  STOSW       { zapisz CX razy AX do ES:[DI], Inc(DI, 2) }
END; { ClsBuf }

PROCEDURE GetMemBuf(VAR Buf :Pointer);
BEGIN
 IF MaxAvail < 64000 THEN Exit;
 GetMem(Buf, 64000);
END; { GetMemBuf }

Do ustalania kiedy trwa powrót pionowy służy bit nr 5 wartości odczytanej z portu 3DA.

PROCEDURE PowrotPionowy; ASSEMBLER;
ASM
   MOV   DX, 3DAh
 @@1:                     { jest powrot    }
   IN    AL, DX
   TEST  AL, 00001000b
   JNZ   @@1
 @@2:                     { nie ma powrotu }
   IN    AL, DX
   TEST  AL, 00001000b
   JZ    @@2
END; { PowrotPionowy }

Naprzemienne wyświetlanie obiektów odbywa się w pętli REPEAT..UNTIL, gdzie warunkiem przerwania działania programu jest naciśniecie klawisza (jakiegokolwiek).

PROCEDURE TProg.Run;
BEGIN
 REPEAT
  { zapetlamy poszczegolne sekwencje animacji }
  Roj^.Init(T2);
  Roj^.Morph(T1, T2, T3);
  Roj^.RotateMorph(T3, Count);

  Kula^.Init(T1);
  Kula^.Morph(T3, T1, T2);
  Kula^.RotateMorph(T2, Count);

  Stozek^.Init(T1);
  Stozek^.Morph(T2, T1, T3);
  Stozek^.RotateMorph(T3, Count);

  Spirala^.Init(T1);
  Spirala^.Morph(T3, T1, T2);
  Spirala^.RotateMorph(T2, Count);

  Szescian^.Init(T1);
  Szescian^.Morph(T2, T1, T3);
  Szescian^.RotateMorph(T3, Count);

  Plaszczyzna^.Init(T1);
  Plaszczyzna^.Morph(T3, T1, T2);
  Plaszczyzna^.RotateMorph(T2, Count);

  Roj^.Init(T3);
  Roj^.Morph(T2, T3, T1);
 UNTIL KeyPressed;  { gdy naciskamy klawisz to koniec zabawy }
END; { TProg.Run }

Z racji tego, że stosujemy cieniowanie, warto by zaprogramować paletę kolorów na odcienie szarości. I tu znowu assembler. Jeżeli programujemy w trybie 13h to program tak naprawdę jest jedną wielka wstawką assemblerową J. Do programowania karty graficznej używamy portów 3C8 i 3C9. 3C8 przechowuje indeks koloru, natomiast 3C9 składowe RGB barwy.

PROCEDURE TProg.Paleta; ASSEMBLER;
ASM
 MOV DX, 03C8h  { port[$3C8]                               }
 XOR AX, AX     { zeruj AX                                 }
 OUT DX, AL     { zaczynamy zmieniac palete od wzorca nr 0 }
 INC DX         { DX++  (3C9h)                             }
 MOV CX, 256    { FOR cx := 1 TO 256 DO                    }
@@1:
 OUT DX, AL     { Port[$3C9] := AL                         }
 OUT DX, AL     { Port[$3C9] := AL                         }
 OUT DX, AL     { Port[$3C9] := AL                         }
 INC AH         { AH++                                     }
 MOV AL, AH     { AL := AH                                 }
 SHR AL, 2      { AL >>= 2                                 }
 DEC CX         { CX--                                     }
 JNZ @@1        { IF cx = 0 THEN Break                     }
END;

Destruktor programu sprząta, po naszych ingerencjach. Zwalnia pamięć zajmowaną przez bufor ekranu oraz przez zmienne obiektowe i przywraca tryb tekstowy 3h.

DESTRUCTOR TProg.Done;
BEGIN
 SetVgaMode(NormalMode);  { tryb 320x200x256 OFF        }
 FreeMemBuf(Buf);         { zwalniamy pamiec bufora     }
 Dispose(Roj, Done);      { zwalniamy zmienne obiektowe }
 Dispose(Kula, Done);
 Dispose(Stozek, Done);
 Dispose(Spirala, Done);
 Dispose(Szescian, Done);
 Dispose(Plaszczyzna, Done);
END; { TProg.Done }

FreeMemBuf to nic innego jak:

PROCEDURE FreeMemBuf(VAR Buf :Pointer);
BEGIN
 FreeMem(Buf, 64000);
END; { FreeMemBuf }

Teraz pozostało napisać to co ma znaleźć się pomiędzy blokami BEGIN..END czyli:

VAR Prog :TProg;

BEGIN
 Prog.Init;
 Prog.Run;
 Prog.Done;
END.

Link do kodów źródłowych oraz silnika graficznego: http://download.4programmers.net/Morphing_3D

Zobacz też:
NeHe Productions: Lesson 25 - Morphing

4 komentarzy

  1. proponuję zamienić słowa kluczowe na małe litery ;)
  2. Jak chcesz umieścić tekst w kategorii Turbo Pascal to "tytuł", czyli ścieżka brzmi: "Turbo Pascal/Tytuł..." - albo np. "Delphi/Artykuły/Nazwa artykułu".

nie myślałem, że jeszcze ktoś pisze w 13h :D stare czasy

//edit
czemu ten art jest jako kategoria?

Link poprawiony :)

Nie wiem dlaczego 3D w linku jest jako zwykły tekst ?