Arkanoid 3D krok po kroku III

02.08.2010 - Olgierd Humeńczuk
TrudnośćTrudność

Implementacja

Teraz posiadamy już wszystkie informacje potrzebne do napisania funkcji generującej wierzchołki, należące do sfery. Zaczniemy od zdefiniowania nowych stałych globalnych:

1
2
3
4
// globalna wartość PI
const double PI = 3.141592;
// globalna wartość 2 * PI
const double T_PI = PI * 2.0;

Kolejnym krokiem będzie implementacja wspomnianej metody normalizującej wektor oraz metody pomocniczej obliczającej jego długość.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class vector3f
{
 
... // pozostała część definicji klasy pozostaje nie zmieniona
 
    /**
     * @brief normalizacja wektora
     * @return znormalizowany wektor
     */
    vector3f normalize( void ) const
    {
        GLfloat len = length();
        assert( len > 0 );
    
        GLfloat one_div_len = 1.0f / len;
    
        return vector3f(
            x * one_div_len,
            y * one_div_len,
            z * one_div_len );
    }    
 
    /**
     * @brief liczenie dlugosci wektora
     * @return dlugosc wektora
     */
    GLfloat length( void ) const
    {
        return sqrtf( x * x + y * y + z * z );
    }
 
... // pozostała definicji klasy pozostaje nie zmieniona
 
};

Następnie napiszemy prostą funkcję pomocniczą, implementującą podane wzory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @brief przeksztalca podane w parametrach wartosci wspolrzednych
 * w ukladzie sferycznym na wartosci wspolrzednych w ukladzie kartezjanskim
 * @param radius promien sfery
 * @param theta dlugosc geograficzna
 * @param phi szerokosc geograficzna
 */
vector3f spherical_2_car( 
      GLfloat radius, GLfloat theta, GLfloat phi )
{
    //warunki wstępne
    assert( radius > 0 );
    assert( theta >= 0 && theta <= T_PI + EPSILON );
    assert( phi >= 0 && phi <= PI + EPSILON );
 
    return vector3f(   radius * sin( theta ) * sin( phi )
                     , radius * cos( phi )
                     , radius * sin( phi ) * cos( theta ) );
}

Pozostała nam do napisania funkcja główna, realizująca algorytm tworzenia siatki sfery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
 * @brief Tworzy siatke sfery dla zadanych parametrow.
 * @param radius promien sfery
 * @param slices liczba podzialow dlugosci geograficznej
 * @param segments liczba podzialow szerokosci geograficznej
 */
vertexes createSphere( GLfloat radius, size_t slices, size_t sides )
{
    //warunki wstępne
    assert( radius > 0 );
 
    vertexes ret;
    ret.reserve( slices * sides * 4 );
 
    for( size_t i = 0; i < slices; ++i )
    {
        GLfloat dslices = i /
            static_cast< GLfloat >( slices );
        GLfloat dslices_pp = ( i + 1 ) /
            static_cast< GLfloat >( slices );
 
        GLfloat phi = PI * dslices;
        GLfloat phi_pp = PI * dslices_pp;
 
        for( size_t j = 0; j < sides; ++j )
        {
            GLfloat dsides = j /
                static_cast< GLfloat >( sides );
            GLfloat dsides_pp = ( j + 1 ) /
                static_cast< GLfloat >( sides );
 
            GLfloat theta = T_PI * dsides;
            GLfloat theta_pp = T_PI * dsides_pp;
 
            ret.push_back(
                vertex3f( spherical_2_car( radius, theta, phi ),
                          spherical_2_car( radius
                            , theta, phi ).normalize() ) );
 
            ret.push_back(
                vertex3f( spherical_2_car( radius, theta, phi_pp ),
                          spherical_2_car( radius
                            , theta, phi_pp ).normalize() ) );
 
            ret.push_back(
                vertex3f( spherical_2_car( radius, theta_pp, phi_pp ),
                          spherical_2_car( radius
                            , theta_pp, phi_pp ).normalize() ) );
 
            ret.push_back(
                vertex3f( spherical_2_car( radius, theta_pp, phi ),
                          spherical_2_car( radius
                            , theta_pp, phi ).normalize() ) );
        }
    }
 
    return ret;
}

Przyjrzyjmy się napisanej przez nas funkcji:

  • linie 12 i 13 to alokacja kontenera oraz pamięci potrzebnej na przechowanie wierzchołków siatki
  • linia 15 to początek pierwszej pętli, iterującej po liczbie podziałów długości geograficznej, czyli wartościach kąta $ \Phi $
  • w liniach od 17 do 23 obliczamy wartość kąta $ \Phi $ dla każdej wartości zmiennej i oraz i + 1.
  • 25 linia to początek pętli iterującej po liczbie podziałów szerokości geograficznej, czyli wartościach kąta $ \Theta $
  • linie od 27 do 33 to obliczenia wartości kąta $ \Theta $ dla każdej wartości zmiennej j oraz j + 1
  • w liniach od 35 do 53, wykorzystując funkcję pomocniczą spherical_2_car oraz normalizację wektora, obliczamy wartości współrzędnych $ x, y, z $ w układzie kartezjańskim i dodajemy powstałe wierzchołki do kontenera

Aby zobaczyć, czy nasza funkcja poprawnie generuje siatkę sfery, musimy dokonać pewnych zmian w kodzie:

  • W funkcji initOpenGL zmienimy linię:
    1
    2
    
                glPolygonMode( GL_FRONT, GL_FILL );
            
    na:
    1
    2
    
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
            
    oraz linię:
    1
    2
    
                glEnable( GL_LIGHTING );
            
    na:
    1
    2
    
                glDisable( GL_LIGHTING );
            

Zmiany te włączą renderowanie wszystkich wielokątów za pomocą linii oraz wyłączą oświetlenie, co pozwoli nam ujrzeć wygenerowaną siatkę.

Następnie użyjemy naszej nowej funkcji, wstawiając ją zamiast wywołania funkcji createCube:  vertexes cubeVertexes = createSphere( 2.0f, 9, 9 ); Powinniśmy otrzymać siatkę sfery o promieniu $ r=2 $ oraz o równej liczbie podziałów długości i szerokości geograficznej $ slices=sides=9 $.

Porządki w kodzie

Wszystkie funkcje i klasy naszej gry mieszczą się w pliku main.cpp. Nie wiem, jak Wam, ale mi zaczyna to powoli przeszkadzać. Czasem zorientowanie się w tym, co musimy dopisać, a co zmienić, zajmuje dłuższą chwilę. Proponuję zatem wprowadzić następujące zmiany:

  • wszystkie funkcje dotyczące obliczeń matematycznych, czy też struktur z nimi związanych np. vector3f , przeniesiemy do pliku math.hpp i w tym pliku implementować będziemy nowe funkcje i struktury związane z obliczeniami stricte matematycznymi
  • funkcje dotyczące tworzenia siatek umieścimy w pliku mesh.hpp
  • kod, który dotyczy renderowania i zarządzania stanami biblioteki OpenGL, przeniesiemy do pliku renderer.hpp
  • funkcje i klasy, które za chwilę napiszemy, umieścimy w pliku scene.hpp

Brawo! Właśnie dokonaliśmy pierwszej refaktoryzacji kodu naszej gry. Tak w żargonie programistów określa się czynności związane z porządkowaniem kodu.

Co dalej?

Mamy napisane trzy nowe funkcje:

  • createPlane - tworzy siatkę płaszczyzny
  • createRectangular- tworzy siatkę prostopadłościanu
  • createSphere - tworzy siatkę sfery
Umiemy za ich pomocą tworzyć i wyświetlać bryły. Wiemy także jak określać kolory materiałów i świateł. Następny problem, z którym musimy sobie poradzić, to: Jak zaimplementować zarządzanie aktorami ( czyli elementami sceny, np. rakietką, kulą itp. ) i sceną, aby możliwie łatwo i szybko aktualizować położenie obiektów i obliczać kolizje między nimi ?

5
Twoja ocena: Brak Ocena: 5 (2 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com