Koło informatyczne 2013

Tworzenie definicji obiektów 3D można wykonać poza programem graficznym. W takim przypadku wyniki umieszcza się w pliku o ściśle określonej strukturze. Otrzymane pliki można następnie używać wielokrotnie w różnych programach graficznych - nawet da się w ten sposób utworzyć własną bibliotekę obiektów 3D. Oczywiście ma to większy sens tylko wtedy, gdy poznamy wszystkie właściwości OpenGL i będziemy potrafili z nich korzystać. Dlatego na początek napiszemy proste programy tworzące takie pliki dla obiektów, które wykorzystują tylko kolorowanie wierzchołków lub ścian. Później rozszerzymy je na tekstury i modelowanie oświetlenia.

Najpierw określmy zawartość pliku definiującego obiekt 3D. Będzie to zwykły plik tekstowy, który można edytować nawet w Notatniku Windows lub w dowolnym innym edytorze tekstowym. Plik będzie się składał z ciągu liczb całkowitych i rzeczywistych.

 

Zawartość Opis
VN Liczba całkowita określająca ilość wierzchołków w obiekcie 3D.
(x y z) x VN VN trójek liczb rzeczywistych, które są współrzędnymi kolejnych wierzchołków figury.
CN Liczba całkowita określająca ilość definicji kolorów. Kolory mogą się odnosić do wierzchołków lub do ścian.
(r g b) x CN CN trójek liczb całkowitych, które określają poszczególne składowe kolorów.
SN Liczba całkowita określająca liczbę wierzchołków definiujących ściany.
v x SN SN liczb całkowitych, które określają numery wierzchołków tworzących ściany.
FN Liczba całkowita określająca ilość ścian
(typ n poz) x FN FN trójek liczb całkowitych, które określają kolejno:
typ – typ ściany
n – ilość wierzchołków w ścianie
poz – pozycja definicji ściany w zbiorze S

 

Umówmy się dodatkowo, iż definicje figur będą tak tworzone, aby środek figury znajdował się w środku układu współrzędnych.

Pierwszy program utworzy definicję prostopadłościanu. Figura taka zbudowana jest z sześciu ścian będącymi prostokątami.

 

obrazek

 

Specjalnie na początek wybraliśmy prostopadłościan, aby skupić się na cechach programu edycyjnego, a nie na samej figurze. Aplikacja, którą napiszemy, będzie wzorcem do tworzenia dalszych edytorów. Przy okazji pokażemy obsługę plików w Borland C++ Builder oraz sposób wyświetlania grafiki OpenGL w innym komponencie graficznym okna, mianowicie w panelu. Na początek tworzymy interfejs graficzny.

Utwórz nowy projekt OpenGL.

W oknie aplikacji umieść następujące komponenty (oprócz komponentu Timer, który powinien już być obecny):

 

  2 x Button z palety Standard
  1 x ColorDialog z palety Dialogs
  3 x Edit z palety Standard
15 x Label z palety Standard
  9 x Panel z palety Standard
  2 x RadioGroup z palety Standard
  1 x SaveDialog z palety Dialogs
  4 x ScrollBar z palety Standard

 

Uważnie ustaw własności poszczególnych komponentów:

 

Form1

BorderIcons→biMaximize = false
BorderStyle = bsSingle
Caption = Generator prostopadłościanu
ClientHeight = 473
ClientWidth = 673
Name = frm3D
Position = poScreenCenter

 

Button1

Caption = Zapisz
Left = 480
Name = btnSave
Top = 432
Width = 91

Button2

Caption = Koniec
Left = 576
Name = btnQuit
Top = 432
Width = 91

 

ColorDialog1

Name = cldColor

 

Edit1

Left = 496
Name = edtA
Text = 1
Top = 208
Width = 172

Edit2

Left = 496
Name = edtB
Text = 1
Top = 232
Width = 172

Edit3

Left = 496
Name = edtC
Text = 1
Top = 256
Width = 172

 

Label1

Caption = a =
Left = 472
Name = lblA
Top = 210

Label2

Caption = b =
Left = 472
Name = lblB
Top = 234

Label3

Caption = c =
Left = 472
Name = lblC
Top = 258

Label4

Caption = 0
Left = 485
Name = lblC0
Top = 288

Label5

Caption = 1
Left = 509
Name = lblC1
Top = 288

Label6

Caption = 2
Left = 533
Name = lblC2
Top = 288

Label7

Caption = 3
Left = 557
Name = lblC3
Top = 288

Label8

Caption = 4
Left = 581
Name = lblC4
Top = 288

Label9

Caption = 5
Left = 605
Name = lblC5
Top = 288

Label10

Caption = 6
Left = 629
Name = lblC6
Top = 288

Label11

Caption = 7
Left = 653
Name = lblC7
Top = 288

Label12

Caption = Odległość
Left = 472
Name = lblD
Top = 152

Label13

Caption = Obrót w osi OX
Left = 472
Name = lblX
Top = 8

Label14

Caption = Obrót w osi OY
Left = 472
Name = lblY
Top = 56

Label15

Caption = Obrót w osi OZ
Left = 472
Name = lblZ
Top = 104

 

Panel1

Caption =
Color = clMaroon
Height = 41
Left = 480
Name = pnlC0
Tag = 0
Top = 304
Width = 17

Panel2

Caption =
Color = clGreen
Height = 41
Left = 504
Name = pnlC1
Tag = 1
Top = 304
Width = 17

Panel3

Caption =
Color = clOlive
Height = 41
Left = 528
Name = pnlC2
Tag = 2
Top = 304
Width = 17

Panel4

Caption =
Color = clNavy
Height = 41
Left = 552
Name = pnlC3
Tag = 3
Top = 304
Width = 17

Panel5

Caption =
Color = clPurple
Height = 41
Left = 576
Name = pnlC4
Tag = 4
Top = 304
Width = 17

Panel6

Caption =
Color = clTeal
Height = 41
Left = 600
Name = pnlC5
Tag = 5
Top = 304
Width = 17

Panel7

Caption =
Color = clRed
Height = 41
Left = 624
Name = pnlC6
Tag = 6
Top = 304
Width = 17

Panel8

Caption =
Color = clAqua
Height = 41
Left = 648
Name = pnlC7
Tag = 7
Top = 304
Width = 17

Panel9

Caption =
Height = 457
Left = 8
Name = pnlGL
Top = 8
Width = 457

 

RadioGroup1

Caption = Tryb koloru
Height = 49
Items = dodać dwa wiersze: Ściany i Wierzchołki
Left = 480
Name = rdgColor
Top = 360
Width = 89

RadioGroup2

Caption = Widok
Height = 49
Items = dodać dwa wiersze: Ściany i Krawędzie
Left = 576
Name = rdgView
Top = 360
Width = 89

 

ScrollBar1

Left = 472
Max = 100
Min = 0
Name = scbD
Top = 176
Width = 193

ScrollBar2

Left = 472
Max = 360
Min = 0
Name = scbX
Top = 32
Width = 193

ScrollBar3

Left = 472
Max = 360
Min = 0
Name = scbY
Top = 80
Width = 193

ScrollBar4

Left = 472
Max = 360
Min = 0
Name = scbZ
Top = 128
Width = 193

 

SaveDialog1

DefaultExt = txt
FileName = cube0001
InitialDir = .
Name = svdSave

 

Timer1

Interval = 10
Name = tmrAnimate

 

Jeśli wszystko wpisałeś poprawnie, to twój interfejs powinien wyglądać następująco (w Windows XP):

 

obrazek

 

Suwaki u góry będą umożliwiały obracanie obiektu w wybranych osiach oraz oddalanie lub przybliżanie go do obserwatora. W polach tekstowych będzie można wpisywać pożądane długości boków. Poniżej określimy kolory ścian lub wierzchołków. Dalej ustalimy, czy kolory będą się odnosiły do ścian, czy do wierzchołków. Widok będzie zmieniał sposób prezentacji naszej figury. Przycisk Zapisz pozwoli zapisać w pliku definicję obiektu. Główny panel będzie używany do wyświetlania sceny OpenGL.

Teraz musimy dokonać kilku zmian w naszym kodzie, aby biblioteka OpenGL wyświetlała grafikę nie na obszarze okna, lecz w panelu pnlGL. Na szczęście jest to bardzo proste do uzyskania. Dwie poniższe funkcje wymagają drobnej modyfikacji:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::FormCreate(TObject *Sender)
{
  hdc = GetDC(pnlGL->Handle);  // Zmiana !!!
  SetPixelFormatDescriptor();
  hrc = wglCreateContext(hdc);
  wglMakeCurrent(hdc, hrc);
  SetupRC();
}
//---------------------------------------------------------------------------
...

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::FormResize(TObject *Sender)
{
  glViewport(0, 0, pnlGL->Width, pnlGL->Height);   // Zmiana !!!
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0f,(GLfloat)pnlGL->Width/(GLfloat)pnlGL->Height,0.1f,300.0f);  // Zmiana !!!
  glMatrixMode(GL_MODELVIEW);
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);   // kolor tła
  glEnable(GL_DEPTH_TEST);  // włącza bufor głębokości
  glDepthFunc(GL_LESS);
  glEnable(GL_CULL_FACE);   // włącza opcję eliminacji ścian
  glFrontFace(GL_CW);       // ściany o wierzchołkach ułożonych zgodnie z ruchem wskazówek
                            // zegara będą traktowane jako zwrócone przodem
  glCullFace(GL_BACK);      // pomija rysowanie ścian odwróconych tyłem
}
//---------------------------------------------------------------------------

 

W pierwszej funkcji FormCreate() zmieniamy kontekst z powierzchni rysunkowej okna na powierzchnię rysunkową panelu pnlGL. Od tego momentu operacje graficzne OpenGL będą wykonywane w obszarze zajętym przez panel.

W drugiej funkcji FormResize() (w tym programie jest ona wykonywana tylko jeden raz, ponieważ okno ma zablokowany rozmiar) dokonujemy dwóch zmian, które uwzględniają rozmiar panelu.

Jeśli teraz skompilujesz program, to po uruchomieniu w panelu powinien pojawić się biały trójkąt na czarnym tle:

 

obrazek

 

Jeśli otrzymałeś taki jak powyżej obraz, to jesteś gotowy do wprowadzania kodu. W przeciwnym razie wróć na początek i sprawdź krok po kroku kolejne czynności.

 

Ponieważ dane figury muszą być dostępne w kilku miejscach, zdefiniujemy je globalnie na początku programu. Umówmy się, że wierzchołki będą określone następująco:

 

obrazek

 

Początkowo niech wszystkie boki będą równe 1. Wtedy współrzędne punktów są następujące (pamiętaj, że środek figury jest w środku układu współrzędnych):

 

v0 = { 0.5f,-0.5f, 0.5f}
v1 = {-0.5f,-0.5f, 0.5f}
v2 = {-0.5f, 0.5f, 0.5f}
v3 = { 0.5f, 0.5f, 0.5f}
v4 = { 0.5f,-0.5f,-0.5f}
v5 = {-0.5f,-0.5f,-0.5f}
v6 = {-0.5f, 0.5f,-0.5f}
v7 = { 0.5f, 0.5f,-0.5f}

 

Definicje kolorów początkowych będą podane jako tablica wartości RGB. Każdy kolor będzie się składał z trzech bajtów, po jednym dla kolejnych barw składowych R, G i B.

 

c0 = {0x80, 0x00, 0x00}   clMaroon
c1 = {0x00, 0x80, 0x00}   clGreen
c2 = {0x80, 0x80, 0x00}   clOlive
c3 = {0x00, 0x00, 0x80}   clNavy
c4 = {0x80, 0x00, 0x80}   clPurple
c5 = {0x00, 0x80, 0x80}   clTeal
c6 = {0xFF, 0x00, 0x00}   clRed
c7 = {0x00, 0xFF, 0xFF}   clAqua

 

Figura będzie posiadała 6 ścian. Każda ściana zawiera 4 wierzchołki (to się nie będzie zmieniać)

 

s0 ...s3  = {0,1,2,3}   przednia
s4 ...s7  = {3,2,6,7}   górna
s8 ...s11 = {7,6,5,4}   tylnia
s12...s15 = {4,5,1,0}   spodnia
s16...s19 = {0,3,7,4}   prawa
s20...s23 = {1,5,6,2}   lewa

 

Definicje ścian (to się nie będzie zmieniać)

 

f0 = { GL_QUADS, 4, 0}  przednia
f1 = { GL_QUADS, 4, 4}  górna
f2 = { GL_QUADS, 4, 8}  tylnia
f3 = { GL_QUADS, 4,12}  spodnia
f4 = { GL_QUADS, 4,16}  prawa
f5 = { GL_QUADS, 4,20}  lewa

 

Dodaj na samym początku programu:

 

//---------------------------------------------------------------------------

#include <vcl.h>

#include <fstream>
using namespace std;

#pragma hdrstop

#include "glunit.h"
//---------------------------------------------------------------------------
 

Wprowadź na początek programu (tzn. przed wszystkimi funkcjami) definicje następujących zmiennych globalnych:

 

// Zmienne globalne

// Wierzchołki
GLfloat V[][3] = {{ 0.5f,-0.5f, 0.5f},
                  {-0.5f,-0.5f, 0.5f},
                  {-0.5f, 0.5f, 0.5f},
                  { 0.5f, 0.5f, 0.5f},
                  { 0.5f,-0.5f,-0.5f},
                  {-0.5f,-0.5f,-0.5f},
                  {-0.5f, 0.5f,-0.5f},
                  { 0.5f, 0.5f,-0.5f}};

// Kolory
GLubyte C[][3] = {{0x80, 0x00, 0x00}, // clMaroon
                  {0x00, 0x80, 0x00}, // clGreen
                  {0x80, 0x80, 0x00}, // clOlive
                  {0x00, 0x00, 0x80}, // clNavy
                  {0x80, 0x00, 0x80}, // clPurple
                  {0x00, 0x80, 0x80}, // clTeal
                  {0xFF, 0x00, 0x00}, // clRed
                  {0x00, 0xFF, 0xFF}}; // clAqua

// Wierzchołki ścian
int S[] = {0,1,2,3,   // przednia
           3,2,6,7,   // górna
           7,6,5,4,   // tylna
           4,5,1,0,   // spodnia
           0,3,7,4,   // prawa
           1,5,6,2};  // lewa

// Definicje ścian
int F[][3] = {{GL_QUADS, 4, 0},  // przednia
              {GL_QUADS, 4, 4},  // górna
              {GL_QUADS, 4, 8},  // tylnia
              {GL_QUADS, 4,12},  // spodnia
              {GL_QUADS, 4,16},  // prawa
              {GL_QUADS, 4,20}}; // lewa

 

Teraz umieść również na początku programu funkcję rysującą obiekt:

 

//---------------------------------------------------------------------------
void Figure3D(int n, bool cmode, GLfloat v[][3], int s[], GLubyte c[][3], int f[][3])
{
  for(int i = 0; i < n; i++)
  {
    if(cmode) glColor3ubv(c[i]);
    glBegin(f[i][0]);
      for(int j = 0; j < f[i][1]; j++)
      {
        if(!cmode) glColor3ubv(c[s[f[i][2]+j]]);
        glVertex3fv(v[s[f[i][2]+j]]);
      }
    glEnd();
  }
}
//---------------------------------------------------------------------------

 

Wykorzystujemy w niej nową funkcję glColor3ubv(), której argumentem jest wskaźnik do obszaru pamięci przechowującego trzy składowe kolorów w postaci bajtów, czyli liczb o zakresie od 0 do 255. Wskaźnikiem tym jest wiersz tablicy kolorów C. Poza tym szczegółem funkcja niczym nie różni się od swojej poprzedniczki.

Zmieniamy treść funkcji Timera, która zajmuje się rysowaniem obiektu w OpenGL.

 

  // Tutaj umieszczamy program dla OpenGL

  if(rdgView->ItemIndex) glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
  else                   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
  
  glTranslatef(0,0,-2 - scbD->Position/10.0);
  glRotatef(scbX->Position,1,0,0);
  glRotatef(scbY->Position,0,1,0);
  glRotatef(scbZ->Position,0,0,1);

  Figure3D(6,!rdgColor->ItemIndex,V,S,C,F);

  // Koniec kodu dla OpenGL

 

Teraz umieścimy w programie funkcje obsługujące zdarzenia komponentów. Pamiętaj, że należy najpierw utworzyć odpowiednią funkcję obsługi zdarzenia za pomocą Object Inspector na zakładce Events, a dopiero wtedy można wprowadzać podany przez nas kod. Inaczej nie będzie to chciało działać.

 

Wybierz panel pnlC0 i utwórz dla niego funkcję obsługi zdarzenia OnClick. Wprowadź do niej poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::pnlC0Click(TObject *Sender)
{
  cldColor->Color = ((TPanel *) Sender)->Color;
  if(cldColor->Execute())
  {
    ((TPanel *) Sender)->Color = cldColor->Color;

    C[((TPanel *) Sender)-> Tag][0] = cldColor->Color & 0xff;
    C[((TPanel *) Sender)-> Tag][1] = ((cldColor->Color) >>  8) & 0xff;
    C[((TPanel *) Sender)-> Tag][2] = ((cldColor->Color) >> 16) & 0xff;
  }
}
//---------------------------------------------------------------------------

 

Funkcja ta wywołuje okienko dialogowe kolorów, w którym użytkownik wybiera dowolny kolor dla ściany lub wierzchołka. Wybrany kolor jest zamieniany na trzy barwy składowe, które są następnie umieszczane w odpowiednim wierszu tablicy kolorów C. Numer wiersza tablicy jest pobierany z pola Tag, ponieważ ta sama funkcja obsługuje pozostałe panele kolorów.

Teraz w Object TreeView kliknij raz myszką w pnlC1. Spowoduje to zaznaczenie komponentu. Wciśnij klawisz Shift, przytrzymaj go wciśniętym i kliknij myszką w pnlC7. Zostaną zaznaczone komponenty od pnlC1 do pnlC7. W Object Inspector na zakładce Events kliknij w strzałkę w dół przy zdarzeniu OnClick i wybierz pnlC0Click. Po tej operacji wszystkie panele od plnC0 do pnlC7 będą wykorzystywały tę samą funkcję obsługi zdarzenia OnClick. Rozróżnienie panelu jest zakodowane w funkcji.

 

Wybierz komponent edtA i utwórz w Object Inspector obsługę zdarzenia OnKeyPress. Zdarzenie to powstaje, gdy w trakcie wpisywania danych do komponentu został naciśnięty klawisz. Wpisz kod do funkcji obsługującej to zdarzenie:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::edtAKeyPress(TObject *Sender, char &Key)
{
  if(Key == 13)
  {
    GLfloat a = StrToFloat(edtA->Text) / 2;
    if(a > 0)
      for(int i = 0; i < 8; i++)
        switch(i)
        {
          case 0: ;
          case 3: ;
          case 4: ;
          case 7:  V[i][0] =  a; break;
          default: V[i][0] = -a; break;
        }
  }
}
//---------------------------------------------------------------------------

 

W obsłudze zdarzenia sprawdzamy, czy został naciśnięty klawisz Enter. Jeśli tak, to odczytujemy tekst z komponentu, zamieniamy go na liczbę, czyli długość boku a i modyfikujemy odpowiednio współrzędne wierzchołków w figurze.

 

Uwaga: część ułamkowa musi być oddzielona przecinkiem od części całkowitej liczby, ponieważ funkcja StrToFloat() korzysta z ustawień narodowych przy odczytywaniu liczb. W Polsce obowiązuje przecinek dziesiętny, natomiast w krajach anglosaskich używa się kropki dziesiętnej.

 

W podobny sposób utwórz funkcję obsługi zdarzenia OnKeyPress dla komponentu edtB i wpisz kod:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::edtBKeyPress(TObject *Sender, char &Key)
{
  if(Key == 13)
  {
    GLfloat b = StrToFloat(edtB->Text) / 2;
    if(b > 0)
      for(int i = 0; i < 8; i++)
        switch(i)
        {
          case 2: ;
          case 3: ;
          case 6: ;
          case 7:  V[i][1] =  b; break;
          default: V[i][1] = -b; break;
        }
  }
}
//---------------------------------------------------------------------------

 

I tak samo dla edtC:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::edtCKeyPress(TObject *Sender, char &Key)
{
  if(Key == 13)
  {
    GLfloat c = StrToFloat(edtC->Text) / 2;
    if(c > 0)
      for(int i = 0; i < 8; i++)
        switch(i)
        {
          case 0: ;
          case 1: ;
          case 2: ;
          case 3:  V[i][2] =  c; break;
          default: V[i][2] = -c; break;
        }
  }
}
//---------------------------------------------------------------------------

 

Kliknij szybko myszką w przycisk Koniec i wpisz do funkcji obsługi zdarzenia OnClick:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::btnQuitClick(TObject *Sender)
{
  Close();
}
//---------------------------------------------------------------------------

 

Tak samo zrób z przyciskiem Zapisz:

 

//---------------------------------------------------------------------------
void __fastcall Tfrm3D::btnSaveClick(TObject *Sender)
{
  if(svdSave->Execute())
  {
    ofstream f(svdSave->FileName.c_str());
    int i;

    // V
    f << 8 << endl;
    for(i = 0; i < 8; i++)
      f << V[i][0] << " " << V[i][1] << " " << V[i][2] << endl;

    // C
    f << 8 << endl;
    for(i = 0; i < 8; i++)
      f << (int)C[i][0] << " " << (int)C[i][1] << " " << (int)C[i][2] << endl;

    // S
    f << 24 << endl;
    for(i = 0; i < 24; i += 4)
      f << S[i] << " " << S[i+1] << " " << S[i+2] << " " << S[i+3] << endl;

    // F
    f << 6 << endl;
    for(i = 0; i < 6; i++)
      f << F[i][0] << " " << F[i][1] << " " << F[i][2] << endl;
    f.close();
  }
}
//---------------------------------------------------------------------------

 

W tej funkcji wywołujemy komponent okna dialogowego zapisu, który umożliwia użytkownikowi określenie lokalizacji oraz nazwy pliku do zapisu. Zapis dokonujemy za pomocą strumienia plikowego ofstream. Jest to strumień wyjściowy. Otwieramy go, wykorzystując nazwę pliku z komponentu svdSave. Następnie zapisujemy do strumienia kolejne elementy zgodnie z definicją, którą podaliśmy na początku rozdziału. Na końcu strumień zamykamy.

 

Program jest gotowy.

 

obrazek

 

Przykładowa treść pliku produkowanego przez program (program standardowo tworzy plik w katalogu uruchomieniowym pod nazwą cube0001.txt):

 

8
0.25 -1 0.5
-0.25 -1 0.5
-0.25 1 0.5
0.25 1 0.5
0.25 -1 -0.5
-0.25 -1 -0.5
-0.25 1 -0.5
0.25 1 -0.5
8
255 255 0
255 0 0
255 128 64
128 255 128
0 0 255
0 128 0
255 0 0
0 255 255
24
0 1 2 3
3 2 6 7
7 6 5 4
4 5 1 0
0 3 7 4
1 5 6 2
6
7 4 0
7 4 4
7 4 8
7 4 12
7 4 16
7 4 20
     V – liczba wierzchołków
współrzędne 8 kolejnych wierzchołków







C – liczba definicji kolorów
poszczególne kolory jako 3 kolejne składowe R, G i B.
0 oznacza brak składowej
255 oznacza pełną wartość składowej





S – liczba wierzchołków powiązanych w ściany
wierzchołki tworzące kolejne ściany





F – liczba ścian
kolejne definicje ścian:
7 – GL_QUADS
4 – liczba wierzchołków na ścianę
x – pozycja definicji ściany w S

 

Gdy mamy gotowy edytor figury, stworzymy prostą aplikację, która wczyta obiekt z pliku i wyświetli go w oknie. Do tego celu najlepsza będzie klasa języka C++, którą zdefiniujemy w osobnym pliku. Takie rozwiązanie pozwoli na załadowanie wielu różnych obiektów. Dostęp do funkcji klasy uzyskamy poprzez odpowiedni plik nagłówkowy.

W nowym katalogu utwórz nowy projekt OpenGL. Przekopiuj do tego katalogu plik cube0001.txt z poprzedniego projektu.

Wybierz opcję menu: File → New → Unit. Utworzy ona nowy moduł programu. Program może się składać z wielu modułów. Moduły zwykle realizują osobne zadania. Dodatkowo modułu mogą być wykorzystywane w innych programach. Utworzony moduł zapisz pod nazwą glclass.

W oknie edytora wybierz zakładkę glclass.cpp i wciśnij Ctrl-F6. Spowoduje to zastąpienie w edytorze pliku glclass.cpp jego plikiem nagłówkowym glclass.h. Umieść w tym pliku poniższy kod:

 

//---------------------------------------------------------------------------

#ifndef glclassH
#define glclassH

#include <Classes.hpp>

//---------------------------------------------------------------------------

class ObjectGL
{
  public:
    float         * V; // wierzchołki
    unsigned char * C; // kolory
    int           * S; // ściany
    int           * F; // ściany

    int nv,nc,ns,nf;

    ObjectGL(AnsiString fn);
    ~ObjectGL();
    void Load(AnsiString fn);
    void Show(bool cmode);
    
  private:
    void Clear();
};

#endif

 

Ponownie wciśnij klawisze Ctrl-F6, aby powrócić w edytorze do pliku glclass.cpp. Wprowadź kod:

 

//---------------------------------------------------------------------------

#pragma hdrstop

#include "glclass.h"
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>

#include <gl/gl.h>
#include <gl/glu.h>
#include <fstream>

using namespace std;

//---------------------------------------------------------------------------

#pragma package(smart_init)

// Konstruktor
ObjectGL::ObjectGL(AnsiString fn)
{
  nv = nc = ns = nf = 0;
  Load(fn);
}

// Destruktor
ObjectGL::~ObjectGL()
{
  Clear();
}

// Ładuje obiekt z pliku
void ObjectGL::Load(AnsiString fn)
{
  Clear();

  ifstream f(fn.c_str(),ios_base::in);
  int i, n, c;

  if(f.good())
  {
    f >> nv;
    n = 3 * nv;
    V = new float [n];
    for(i = 0; i < n; i++) f >> V[i];

    f >> nc;
    n = 3 * nc;
    C = new unsigned char[n];
    for(i = 0; i < n; i++)
    {
      f >> c;
      C[i] = c;
    }

    f >> ns;
    S = new int[ns];
    for(i = 0; i < ns; i++) f >> S[i];

    f >> nf;
    n = 3 * nf;
    F = new int[n];
    for(i = 0; i < n; i++) f >> F[i];
  }
  f.close();
}

// Wyświetla obiekt
void ObjectGL::Show(bool cmode)
{
  for(int i = 0; i < nf; i++)
  {
    if(cmode) glColor3ubv(&C[3*i]);

    int ftyp = F[3 * i];
    int fnum = F[3 * i + 1];
    int fpos = F[3 * i + 2];

    glBegin(ftyp);
      for(int j = 0; j < fnum; j++)
      {
        int pos = 3 * S[fpos + j];
        if(!cmode) glColor3ubv(&C[pos]);
        glVertex3fv(&V[pos]);
      }
    glEnd();
  }
}

// Usuwa zawartość tablic
void ObjectGL::Clear()
{
  if(nv)
  {
    delete [] V;
    nv = 0;
  }

  if(nc)
  {
    delete [] C;
    nc = 0;
  }

  if(ns)
  {
    delete [] S;
    ns = 0;
  }

  if(nf)
  {
    delete [] F;
    nf = 0;
  }
}

 

Konstruktor tworzy obiekt trójwymiarowy. Parametrem konstruktora jest nazwa pliku z definicją obiektu.

Destruktor jest wywoływany, gdy klasa przestaje istnieć w programie. Usuwa on tablice dynamiczne, które zawierają definicję obiektu.

Funkcja Load() ładuje definicję obiektu z pliku, którego nazwę przekazano jako argument.

Funkcja Show() wyświetla obiekt w aktualnym oknie OpenGL. Zasadniczo funkcja ta nie różni się w działaniu od podanych wcześniej funkcji.

Funkcja Clear() usuwa tablice dynamiczne.

Dane są przechowywane we wewnętrznych tablicach V, C, S i F.

 

W kolejnym kroku przyłączymy moduł do programu głównego. Przejdź do zakładki glunit.cpp i na początku programu dopisz:

 

//---------------------------------------------------------------------------

#include <vcl.h>

#pragma hdrstop

#include "glunit.h"
#include "glclass.h"

//---------------------------------------------------------------------------

 

W ten sposób moduł podstawowy uzyska dostęp do klasy ObjectGL zdefiniowanej w module glclass.cpp. W zmiennych globalnych umieść:

 

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
Tfrm3D *frm3D;

ObjectGL fig("cube0001.txt");

//---------------------------------------------------------------------------

 

Wpis ten tworzy zmienną fig klasy ObjectGL. Konstruktor klasy automatycznie odczyta definicję obiektu z podanego pliku. Ostatnią rzeczą w testowym programie jest wpisanie kodu OpenGL w funkcji obsługi Timera:

 

  // Tutaj umieszczamy program dla OpenGL

  static GLfloat alphax = 0;
  static GLfloat alphay = 0;
  static GLfloat alphaz = 0;

  glTranslatef(0.0f,0.0f,-3.0f);
  glRotatef(alphax,1,0,0);
  glRotatef(alphay,0,1,0);
  glRotatef(alphaz,0,0,1);

//  glPolygonMode(GL_FRONT,GL_LINE);

  fig.Show(true);

  alphax += 0.5; if(alphax >= 360) alphax = 0;
  alphay += 0.2; if(alphay >= 360) alphay = 0;
  alphaz += 0.3; if(alphaz >= 360) alphaz = 0;
  
  // Koniec kodu dla OpenGL

 

Skompiluj i uruchom program.

 

obrazek  obrazek  obrazek

 

Nowe funkcje

glColor3ubv() – ustawia kolor wg 3 wartości 8-bitowych RGB. Parametrem funkcji jest adres obszaru przechowującego te wartości.

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe