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
-
w liniach od 17 do 23 obliczamy wartość kąta 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
-
linie od 27 do 33 to obliczenia wartości kąta 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
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 oraz o
równej liczbie podziałów długości i szerokości geograficznej .
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 ?