|
Definice hodnot scény
InitScene
LoadBSP
CreateLightmapTexture
DrawScene
FindLeaf
CalculateFrustum
IsClusterVisible
BoxInFrustum
RenderFace
glVertexPointer(3, GL_FLOAT, ..., ...);
glTexCoordPointer(2, GL_FLOAT, ..., ...);
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glActiveTextureARB(GL_TEXTURE0_ARB);
glActiveTextureARB(GL_TEXTURE1_ARB);;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, ...);
glDrawArrays(GL_TRIANGLE_FAN, ..., ...);
glGetFloatv( GL_PROJECTION_MATRIX, ... );
glGetFloatv( GL_MODELVIEW_MATRIX, ... );
glGenTextures(1, ...);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, i_width, i_height, GL_RGB, GL_UNSIGNED_BYTE, pby_image_bits);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
|
V tomto tutorialu si zkusíme načíst a vykreslit Quake III scénu (*.bsp).
Co jsou BSP stromy ?
BSP stromy (Binary Space Partitioning Trees) je strom bitových informací o scéně. Scéna
je rozdělena na mnoho částí tzv. listy (leafs). Podle pozice kamery pak počítáme
ve kterém listu se nachází a na které listy kamera míří. Tyto listy se vykreslují, ostatní se
nevykreslují, protože stejně nejsou vidět. Tato technika podstatně urychlí překreslování scény.
Jak se BSP strom tvoří ?
Vezme se celá scéna. Objekty se uzavřou do tzv. obálek (bounding box). Obálky mají tvar kvádru.
Celou scénu nyní rozpůlíme na dvě části.
Vytvoří se dělicí plocha (plane), prvni uzel (node) a dva listy (leaf).
Pokud je v listu jen jedna obálka, tak se dál nebude dělit.
Pokud list obsahuje více obálek, tak se list opět rozpůlí na dvě části, vytvoří se další dělicí plocha (plane),
uzel (node) a nové listy (leafs).
Jak BSP stromem procházet ?
Každý uzel obsahuje informace o dělicích plochách (plane) uzlu (node).
Podle normálových vektorů dělicích ploch a aktuální pozice kamery zjistíme,
ve kterém listu se kamera nachází. Funkce FindLeaf();
Každý list obsahuje bitové (1 = viditelný, 0 = neviditelný) informace (cluster). Kolik je uzlů ve scéně,
tolik bitů má hlavní uzel ((node)kořen stromu).
Pokud má například scéna 10 uzlů (což je velmi malá scéna),
tak hlavní uzel ((node)kořen stromu) bude mít v clusteru uloženo 10 bitů informací,
další dva uzly listu hlavniho uzlu budou mít 9 bitů atd.
Funkce IsClusterVisible() nám zjistí podle bitových hodnot clusteru,
které listy (leafs) se nacházejí kolem listu, ve kterém se kamera nachází.
Nakonec vykreslíme listy ve kterém se kamera nachází a listy, které jsou v zorném úhlu kamery.
Každý list (leaf) by měl obsahovat jednu obálku (viz. vytváření BSP stromu výše).
Rozbalíme obálky, ve kterých jsou detailní informace o objektech. Tyto objekty vykreslíme.
Další informace o BSP stromech
Podrobnější informace o BSP stromech, bohužel však anglicky, najdete na
http://www.opengl.org/developers/code/bspfaq/.
Na těchto stránkách naleznete veškeré informace.
Píše se tam, že BSP strom je binárně rozdělená scéna.
"Binární" znamená, že rodičovský uzel (node)
stromu se vždy dělí na 2 potomky (listy (leafs)).
BSP také obsahuje dělicí plochy
přidružené ke každému rodičovskému uzlu,
které se používají pro stanovení (podle normálového vektoru dělicích ploch)
předního a zadního listu (leafs) uzlu (node) potomků.
Quake3 editor již pro nás BSP strom vytvořil,
nám jen zbývá se v těchto datech orientovat.
V poli uzlů se hledá index listu (leaf), který má zápornou hodnotu, funkce FindLeaf();
Přední list má kladný index, zadní list má záporný index.
Pomocí obecné rovnice roviny (Ax + By + Cz + D = 0) se vypočítá vzdálenost kamery od dělicí plochy.
Pokud vzdálenost je kladná, nebo nulová, tak se počítá další dělicí plocha.
Pokud je vzdálenost záporná, nachází se kamera v těsné blízkosti dělicí plochy
v zadním listu se zápornou hodnotou indexu.
Zápornou hodnotu indexu listu převedem na kladnou použitím binárního operatoru ~, nebo -(i + 1),
abychom ho mohli použít pro detekci listů, které se nacházejí v zorném úhlu kamery, funkce IsClusterVisible() viz. výše.
Ještě nějaké informace získáte na stránkách
http://www.pixelate.co.za/issues/1/1/articles/bsp_trees.htm
, nebo na
http://symbolcraft.com/graphics/bsp/index.html
Podrobný anglický popis Quake III BSP formátu
najdete na www.GameTutorials.com/quake3format.htm.
Autorem je Ben "DigiBen"
Humphrey, který je také autorem tutorialu ze kterého jsem čerpal informace, zdrojáky a anglické poznámky.
Quake III level editor GtkRadiant
si můžete stáhnout ze stránek http://www.gtkradiant.com.
Má však kolem 25MB, což není jistě pozitivní zpráva.
Na stránkách http://www.quake.cz najdete mimo jiné odkazy
na české vývojáře, kteří s GtkRadiantem pracují. V GtkRadiant můžete vytvářet levely do Quake III Areny a nyní
do KEngine. Tento a předešlé OpenGL game tutorialy popisují funkce tohoto enginu.
|
|
|
U všech funkcí je anglický popis od
"DigiBena"
a můj český popis.
Někdy je anglický popis srozumitelnější, proto takto činím.
Nejdříve se podíváme jak vypadá formát Quake III BSP souboru. Skládá se z položek,
které jsou pro přehlednost rozděleny pomocí enumeratorů.
|
|
/////////////////
// Enumeratory //
/////////////////
// This is the number that is associated with a face that is of type "polygon"
// Číslo podle kterého se určuje, jestli je ploška trojúhelník, nebo jiný polygon
enum FACE
{
FACE_POLYGON = 1
};
// This is our lumps enumeration
// Identifikační čísla všech potřebných položek pro načítání
enum LUMPS
{
// Stores player/object positions, etc...
// Identifikátor pro pozice hráčů, objektů atd...
LUMPS_ENTITIES = 0,
// Stores texture information
// Identifikátor textur
LUMPS_TEXTURES,
// Stores the splitting planes
// Ident. dělicích ploch BSP scény
LUMPS_PLANES,
// Stores the BSP nodes
// Ident. uzlů BSP stromu
LUMPS_NODES,
// Stores the leafs of the nodes
// Ident. listů uzlu BSP stromu
LUMPS_LEAFS,
// Stores the leaf's indices into the faces
// Ident. indexů plošek v listu
LUMPS_LEAF_FACES,
// Stores the leaf's indices into the brushes
// Ident. indexů kolizních srážek mezi listy (zatím se nepoužívá)
LUMPS_LEAF_BRUSHES,
// Stores the info of world models
// Ident. modelů scény (zatím se nepoužívá)
LUMPS_MODELS,
// Stores the brushes info (for collision)
// Ident. informací o kolizních srážkách pro kolize (zatím se nepoužívá)
LUMPS_BRUSHES,
// Stores the brush surfaces info
// Ident. informací stran kolizních srážek (zatím se nepoužívá)
LUMPS_BRUSH_SIDES,
// Stores the level vertices
// Ident. vrcholů scény
LUMPS_VERTICES,
// Stores the model vertices offsets
// Ident. skupin vrcholů (zatím se nepoužívá)
LUMPS_MESH_VERTS,
// Stores the shader files (blending, anims..)
// Ident. stínů, které jsou tvořeny jako animováné průhledné plošky (zatím se nepoužívá)
LUMPS_SHADERS,
// Stores the faces for the level
// Ident. plošek scény
LUMPS_FACES,
// Stores the lightmaps for the level
// Ident. světelných map (lightmap) scény
LUMPS_LIGHTMAPS,
// Stores extra world lighting information
// Ident. specielních informací o světlech (zatím se nepoužívá)
LUMPS_LIGHT_VOLUMES,
// Stores (PVS Potential Visibility Sets) and cluster info (visibility)
// Ident. bitových informací viditelnosti
LUMPS_VIS_DATA,
// A constant to store the number of lumps
// Počet indentifikátorů položek scény
LUMPS_MAX_LUMPS
};
|
Dále vytvoříme potřebné struktury a proměnné.
|
|
///////////////
// Struktury //
///////////////
// This is our BSP header structure
// Hlavička souboru BSP
struct TBspHeader
{
// This should always be 'IBSP'
// Identifikátor, který musí být vždy řetězec IBSP
char pc_id[4];
// This should be 0x2e for Quake 3 files
// Verze, která musí mít hodnotu 0x2e
int i_version;
};
// This is our BSP lump structure
// Struktura BSP identifikátorů
struct TBspLump
{
// The offset into the file for the start of this lump
// Informace potřebná při načítání BSP souboru,
// podle které se určuje, která z položek scény se má právě načítat
int i_offset;
// The length in bytes for this lump
// Celková velikost identifikátorů
int i_length;
};
// This is our BSP vertex structure
// Struktura vrcholů BSP scény
struct TBspVertex
{
// (x, y, z) position.
// xyz pozice
float pf_position[3];
// (u, v) texture coordinate
// Texturová koordinace
float pf_texture_coord[2];
// (u, v) lightmap coordinate
// Texturová koordinace světelných map (lightmap)
float pf_lightmap_coord[2];
// (x, y, z) normal vector
// Normálový vektor vrcholu
float pf_normal[3];
// RGBA color for the vertex
// RGBA barevná hodnota vrcholu
byte pby_color[4];
};
// This is our BSP face structure
// Struktura plošek BSP scény
struct TBspFace
{
// The index into the texture array
// Identifikáční číslo textury
int i_texture_id;
// The index for the effects (or -1 = n/a)
// Identifikační číslo efektů (hodnoty se zatím nenačítají)
int i_effect;
// 1=polygon, 2=patch, 3=mesh, 4=billboard
// Typ plošky viz. výše
int i_type;
// The starting index into this face's first vertex
// Index prvního vrcholu plošky
int i_start_vert_index;
// The number of vertices for this face
// Počet vrcholů plošky
int i_num_of_verts;
// The index into the first meshvertex
// Index prvního vrcholu skupiny vrcholů (hodnoty se zatím nenačítají)
int i_mesh_vert_index;
// The number of mesh vertices
// Počet vrcholů ve skupině vrchlolů (hodnoty se zatím nenačítají)
int i_num_mesh_verts;
// The texture index for the lightmap
// Identifikační číslo lightmap textury
int i_lightmap_id;
// The face's lightmap corner in the image
// Rohové plošky lightmap (hodnoty se zatím nenačítájí)
int pi_lightmap_corner[2];
// The size of the lightmap section
// Velikost lightmap částice (hodnoty se zatím nenačítají)
int pi_lightmap_size[2];
// The 3D origin of lightmap.
// Pozice 3D lightmapy (hodnoty se zatím nenačítají)
float pf_lightmap_pos[3];
// The 3D space for s and t unit vectors.
// 3D prostor pro "st! vektorové jednotky 3D lightmap (hodnoty se zatím nenačítají)
float ppf_lightmap_vectors[3][2];
// The face normal.
// Normálový vektor plošky (hodnoty se zatím nenačítají)
float pf_normal[3];
// The bezier patch dimensions.
// Velikost bezierovy plochy (hodnoty se zatím nenačítají)
int pi_bezier_size[2];
};
// This is our BSP texture structure
// Struktura textur BSP scény
struct TBspTexture
{
// The name of the texture w/o the extension
// Jméno textury
char pc_filename[64];
// The surface flags (unknown)
// Povrchové vlajky (hodnoty se zatím nenačítají)
int i_flags;
// The content flags (unknown)
// Obsah vlajek (hodnoty se zatím nenačítají)
int i_flag_contents;
};
// This is our BSP lightmap structure which stores the 128x128 RGB values
// Struktura lightmap, které jsou tvořeny o velikosti 128x128 a RGB barev
struct TBspLightmap
{
// The RGB data in a 128x128 image
// Schránka pro RGB hodnoty barev lightmap a jejich velikost 128x128
byte pppby_image_bits[128][128][3];
};
// This stores a node in the BSP tree
// Struktura uzlů BSP stromu
struct TBspNode
{
// The index into the planes array
// Identifikační číslo ploch
int i_plane;
// The child index for the front node
// Identifikační číslo dceřinného předního uzlu
int i_front;
// The child index for the back node
// Identifikační číslo dceřinného zadního uzlu
int i_back;
// The bounding box min position.
// Minimální hodnota kolizního boxu (hodnoty se zatím nenačítají)
int pi_min_box[3];
// The bounding box max position.
// Maximální hodnota kolizního boxu (hodnoty se zatím nenačítají)
int pi_max_box[3];
};
// This stores a leaf (end node) in the BSP tree
// Struktura listů (leaf) uzlů (node) BSP stromu
struct TBspLeaf
{
// The visibility cluster
// Identifikační číslo klastrů (cluster)
// Podle hodnoty clusteru se vyhledávají listy BSP stromu
int i_cluster;
// The area portal
// Rozsah portálu (hodnoty se zatím nenačítají)
int i_area;
// The bounding box min position
// Minimální hodnota kolizního boxu (hodnoty se zatím nenačítají)
int pi_min_box[3];
// The bounding box max position
// Maximální hodnota kolizního boxu (hodnoty se zatím nenačítají)
int pi_max_box[3];
// The first index into the face array
// Identifikační číslo plošky listu
int i_leaf_face;
// The number of faces for this leaf
// Počet plošek listu
int i_num_of_leaf_faces;
// The first index for into the brushes
// Identifikační číslo kolizních srážek (hodnoty se zatím nenačítají)
int i_leaf_brush;
// The number of brushes for this leaf
// Počet kolizních srážek tohoto listu (hodnoty se zatím nenačítají)
int i_num_of_leaf_brushes;
};
// This stores a splitter plane in the BSP tree
// Struktura dělicích ploch BSP stromu
struct TBspPlane
{
// Plane normal.
// Normálový vektor plochy
float pf_normal[3];
// The plane distance from origin
// Vzdálenost od plochy kdy dochází k detekci bodu s dělicí plochou
float f_distance;
};
// This stores the cluster data for the PVS's (Potential Visibility Sets)
// Struktura dat klastrů (cluster) pro PVS (Nastavení možné viditelnosti)
struct TBspVisData
{
// The number of clusters
// Počet klastrů
int i_num_of_clusters;
// The amount of bytes (8 bits) in the cluster's bitset
// Množství bytů (1byte = 8bitů) v klastrové (cluster) informaci
int i_bytes_per_cluster;
// The array of bytes that holds the cluster bitsets
// Řetězec bitových informací klastru
byte *pby_cluster_bitsets;
};
//////////////
// Proměnné //
//////////////
// The number of verts in the model
// Počet vrcholů scény
int m_iNumOfVerts;
// The number of faces in the model
// Počet plošek scény
int m_iNumOfFaces;
// The number of texture maps
// Počet textur
int m_iNumOfTextures;
// The number of light maps
// Počet lightmap textur
int m_iNumOfLightmaps;
// The number of nodes
// Počet uzlů
int m_iNumOfNodes;
// The number of leafs
// Poče listů
int m_iNumOfLeafs;
// The number of leaf faces
// Počet plošek v listu
int m_iNumOfLeafFaces;
// The number of planes
// Počet dělicích ploch
int m_iNumOfPlanes;
// This will store how many faces are drawn and are seen by the camera
// Kontrolní proměnná, která ukládá, kolik plošek se renderuje
int m_iVisibleFaces;
// The object's vertices
// Vrcholy scény
TBspVertex *m_ptBspVertex;
// The faces information of the object
// Plošky scény
TBspFace *m_ptBspFace;
// The nodes information of the BSP scene
// Uzly BSP scény
TBspNode *m_ptBspNode;
// The leafs information of the BSP scene
// Listy BSP scény
TBspLeaf *m_ptBspLeaf;
// The splitter planes of the BSP scene
// Dělicí plochy BSP scény
TBspPlane *m_ptBspPlane;
// The leaf faces of the BSP scene
// Plošky listů BSP scény
int *m_piLeafFaces;
// The clusters of the BSP scene
// Data pro detekci viditelnosti portálů
TBspVisData m_tBspVisData;
// The texture and lightmap array for the level
// Textury BSP scény
UINT m_uiTextures[MAX_TEXTURES];
// The lightmap texture array
// Textura lightmapy
UINT m_uiLightmaps[MAX_TEXTURES];
// The bitset for the faces that have/haven't been drawn
// Proměnná pro detekci, zda má být ploška vykreslena či ne
K_CBitset m_BitsetFacesDrawn;
K_CTexture m_Texture; // Textura
K_CFrustum m_Frustum; // Viditelnost v zorném úhlu kamery
|
Nyní když všechny hodnoty jsou definované, tak ve třídě
K_CScene ve funkci InitScene()
načteme globální proměnné aplikace. Pak si vytvoříme objekt m_pQuake3Bsp a
budememe volat funkci LoadBSP() pro načítání všech hodnot BSP scény, ze
souboru definovaném v Config.ini
|
|
// Načti scénu
bool K_CScene::InitScene(HDC hdc, HWND hwnd, int iScreenWidth, int iScreenHeight, int iColorBits, int iDepthBits)
{
...
...
...
// Enable front face culling, since that's what Quake3 does
glCullFace(GL_FRONT);
glEnable(GL_CULL_FACE);
...
...
...
///////////////////////////////////////////////
//// Načítání hodnot ze souboru Config.ini ////
///////////////////////////////////////////////
// Here we open the config file and init some variables
// Otevři soubor Config.ini a načti proměnné
ifstream ini_config("Config.ini"); // Otevři souboru Config.ini
string str_level = ""; // Řetězec jména levelu
string str_gamma = ""; // Řetězec gamma hodnoty lightmap
// Check if the file was not found or could not be opened
// Zjisti, jestli se podařilo otevřít ini soubor
if(ini_config.fail())
{
// Display an error message and quit the program if file wasn't found
// Vyhoď chybovou hlášku
MessageBox(NULL, "Could not find Config.ini file!", "Error", MB_OK);
PostQuitMessage(0); // Ukonči aplikaci
return false; // Vyskoč z funkce
}
// Read in the name of the level that will be loaded
// Načti jméno BSP scény, kterou budeme načítat
ini_config >> str_level >> str_level;
// Now we need to read in the gamma level for our lightmaps
// Načti gamma hodnotu lightmap
ini_config >> str_gamma>> g_fGamma;
// Close the file
// Zavři soubor Config.ini
ini_config.close();
///////////////////////////////////////////////
//// Nastavení ostatních hodnot scény //////////
///////////////////////////////////////////////
// Initialize the multitexturing function pointers
// Nastav ukazatele na multitextorové funkce
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB");
glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB");
// Here we make sure that the functions were loaded properly
// Jestliže grafická karta neumí multitexturing, tak
if(!glActiveTextureARB || !glClientActiveTextureARB)
{
// Display an error message and quit
// Vyhoď chybovou hlášku
MessageBox(NULL, "Your video card doesn't support multitexturing", "Error", MB_OK);
g_bMultitexture = false; // Vypni multitexturing
g_bTextures = true; // Zapni jednoduché texturování
g_bLightmaps = false; // Vypni druhou texturu, lightmapy
}
...
...
...
// Here we load the level and get the result (true == success)
// Načti Quake3 level a vrať true, pokud se to podaří
m_pQuake3Bsp = new K_CQuake3Bsp; // Alokuj paměť pro Quake3 level
bool b_result = m_pQuake3Bsp->LoadBSP(str_level.c_str()); // Načti level
// Make sure the file was found and we received a success
if(b_result == false) // Jestliže se podařilo načíst level, tak
{
// Zobraz chybovou hlášku
K_CViewValue view_value;
view_value.ErrorMsgChar( "Could not open bsp file", str_level.c_str() );
PostQuitMessage(0); // Ukonči aplikaci
return false; // Vyskoč z funkce
}
...
...
...
(
|
Funkce LoadBSP() načítá všechny hodnoty BSP scény.
|
|
// Načti všechny data scény z BSP souboru
bool K_CQuake3Bsp::LoadBSP(const char *pc_filename)
{
FILE *pfile = NULL; // Soubor nastav na NULL
int i = 0; // Proměnnou pro cyklování nastav na 0
// Check if the .bsp file could be opened
// Pokud se napodaří otevřít soubor BSP, tak
if((pfile = fopen(pc_filename, "rb")) == NULL)
{
// Display an error message and quit if the file can't be found.
// Vyhoď chybovou hlášku
MessageBox(NULL, "Could not find BSP file!", "Error", MB_OK);
return false; // Vrať funkci false
}
// Initialize the header and lump structures
TBspHeader t_bsp_header = {0}; // Vymaž hodnoty BSP hlavičky
TBspLump t_bsp_lump[LUMPS_MAX_LUMPS] = {0}; // Vymaž hodnoty BSP identifikačních položek
// Read in the header and lump data
// Načti hodnoty hlavičky a identifikační položky
fread(&t_bsp_header, 1, sizeof(TBspHeader), pfile);
fread(&t_bsp_lump, LUMPS_MAX_LUMPS, sizeof(TBspLump), pfile);
// Now we know all the information about our file. We can
// then allocate the needed memory
//
// Nyní známe všechny informace o souboru.
// Dále budeme alokovat potřebnou paměť pro všechny potřebné proměnné
// Allocate the vertex memory
// Alokuj paměť pro vrcholy
m_iNumOfVerts = t_bsp_lump[LUMPS_VERTICES].i_length / sizeof(TBspVertex);
m_ptBspVertex = new TBspVertex [m_iNumOfVerts];
// Allocate the face memory
// Alokuj paměť pro plošky
m_iNumOfFaces = t_bsp_lump[LUMPS_FACES].i_length / sizeof(TBspFace);
m_ptBspFace = new TBspFace [m_iNumOfFaces];
// Allocate memory to read in the texture information.
// Alokuj paměť pro texturové informace
m_iNumOfTextures = t_bsp_lump[LUMPS_TEXTURES].i_length / sizeof(TBspTexture);
TBspTexture *pt_bsp_textures = new TBspTexture [m_iNumOfTextures];
// Allocate memory to read in the lightmap data.
// Alokuj paměť pro lightmap data
m_iNumOfLightmaps = t_bsp_lump[LUMPS_LIGHTMAPS].i_length / sizeof(TBspLightmap);
TBspLightmap *pt_bsp_lightmaps = new TBspLightmap [m_iNumOfLightmaps ];
// Seek to the position in the file that stores the vertex information
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty vrcholů
fseek(pfile, t_bsp_lump[LUMPS_VERTICES].i_offset, SEEK_SET);
// Go through all of the vertices that need to be read and swap axises
// Projdi všechny vrcholy
for(i = 0; i < m_iNumOfVerts; i++)
{
// Read in the current vertex
// Čti ze souboru BSP hodnoty vrcholů
fread(&m_ptBspVertex[i], 1, sizeof(TBspVertex), pfile);
// Swap the y and z values, and negate the new z so Y is up.
// Prohoď hodnoty Y a Z, a nastav opačné znaménko u nové hodnoty Z
float f_temp = m_ptBspVertex[i].pf_position[1];
m_ptBspVertex[i].pf_position[1] = m_ptBspVertex[i].pf_position[2];
m_ptBspVertex[i].pf_position[2] = -f_temp;
// Negate the V texture coordinate because it is upside down otherwise...
// Nastav opačné znaménko u verikální koordinace, aby textura nebyla vzhůru nohama
m_ptBspVertex[i].pf_texture_coord[1] *= -1;
}
// Seek to the position in the file that stores the face information
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty plošek
fseek(pfile, t_bsp_lump[LUMPS_FACES].i_offset, SEEK_SET);
// Read in all the face information
// Čti ze souboru BSP všechny hodnoty plošek
fread(m_ptBspFace, m_iNumOfFaces, sizeof(TBspFace), pfile);
// Seek to the position in the file that stores the texture information
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty textur
fseek(pfile, t_bsp_lump[LUMPS_TEXTURES].i_offset, SEEK_SET);
// Read in all the texture information
// Čti ze souboru BSP všechny hodnoty textur
fread(pt_bsp_textures, m_iNumOfTextures, sizeof(TBspTexture), pfile);
// Go through all of the textures
// Projdi všechny textury
for(i = 0; i < m_iNumOfTextures; i++)
{
// Find the extension if any and append it to the file name
// Najdi správnou příponu (JPG, TGA) načítaného obrázkového souboru
// a přídej ji k názvu souboru uloženém v pc_filename
m_Texture.FindTextureExtension(pt_bsp_textures[i].pc_filename);
// Create a texture from the image
// Načti texturu
m_uiTextures[i] = m_Texture.LoadGLTexture(pt_bsp_textures[i].pc_filename);
}
// We can now free all the texture information since we already loaded them
// Vymaž všechny texturové hodnoty z paměti
delete [] pt_bsp_textures;
// Seek to the position in the file that stores the lightmap information
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty lightmap
fseek(pfile, t_bsp_lump[LUMPS_LIGHTMAPS].i_offset, SEEK_SET);
// Go through all of the lightmaps and read them in
// Projdi všechny lightmapy
for(i = 0; i < m_iNumOfLightmaps; i++)
{
// Read in the RGB data for each lightmap
// Načti RGB data všech lightmap
fread(&pt_bsp_lightmaps[i], 1, sizeof(TBspLightmap), pfile);
// Create a texture map for each lightmap that is read in. The lightmaps
// are always 128 by 128.
// Vytvoř lightmap texturu která má velikost vždy 128x128
m_Texture.CreateLightmapTexture(m_uiLightmaps[i], (unsigned char *)pt_bsp_lightmaps[i].pppby_image_bits, 128, 128);
}
// Delete the image bits because we are already done with them
// Vymaž všechny lightmap hodnoty z paměti
delete [] pt_bsp_lightmaps;
// Store the number of nodes and allocate the memory to hold them
// Načti počet uzlů a alokuj pro ně paměť
m_iNumOfNodes = t_bsp_lump[LUMPS_NODES].i_length / sizeof(TBspNode);
m_ptBspNode = new TBspNode [m_iNumOfNodes];
// Seek to the position in the file that hold the nodes and store them in m_ptBspNode
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty uzlů a tyto hodnoty načti
fseek(pfile, t_bsp_lump[LUMPS_NODES].i_offset, SEEK_SET);
fread(m_ptBspNode, m_iNumOfNodes, sizeof(TBspNode), pfile);
// Store the number of leafs and allocate the memory to hold them
// Načti počet listů a alokuj pro ně paměť
m_iNumOfLeafs = t_bsp_lump[LUMPS_LEAFS].i_length / sizeof(TBspLeaf);
m_ptBspLeaf = new TBspLeaf [m_iNumOfLeafs];
// Seek to the position in the file that holds the leafs and store them in m_ptBspLeaf
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty listů a tyto hodnoty načti
fseek(pfile, t_bsp_lump[LUMPS_LEAFS].i_offset, SEEK_SET);
fread(m_ptBspLeaf, m_iNumOfLeafs, sizeof(TBspLeaf), pfile);
// Now we need to go through and convert all the leaf bounding boxes
// to the normal OpenGL Y up axis.
// Projdi všechny listy a prohoď hodnoty Y na Z všech obálek listů.
// OpenGL má totiž Y a Z hodnoty prohozeny oproti
// ostatním grafickým programům (např. 3dsMax)
for(i = 0; i < m_iNumOfLeafs; i++)
{
// Swap the min y and z values, then negate the new Z
// Prohoď minimální hodnoty obálky Y na Z.
// U Z hodnoty nastav opačné znaménko
float f_temp = m_ptBspLeaf[i].pi_min_box[1];
m_ptBspLeaf[i].pi_min_box[1] = m_ptBspLeaf[i].pi_min_box[2];
m_ptBspLeaf[i].pi_min_box[2] = -f_temp;
// Swap the max y and z values, then negate the new Z
// Prohoď maximální hodnoty obálky Y na Z.
// U Z hodnoty nastav opačné znaménko
f_temp = m_ptBspLeaf[i].pi_max_box[1];
m_ptBspLeaf[i].pi_max_box[1] = m_ptBspLeaf[i].pi_max_box[2];
m_ptBspLeaf[i].pi_max_box[2] = -f_temp;
}
// Store the number of leaf faces and allocate the memory for them
// Načti počet plošek listů a alokuj pro ně paměť
m_iNumOfLeafFaces = t_bsp_lump[LUMPS_LEAF_FACES].i_length / sizeof(int);
m_piLeafFaces = new int [m_iNumOfLeafFaces];
// Seek to the leaf faces lump, then read it's data
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty plošky lisů a tyto hodnoty načti
fseek(pfile, t_bsp_lump[LUMPS_LEAF_FACES].i_offset, SEEK_SET);
fread(m_piLeafFaces, m_iNumOfLeafFaces, sizeof(int), pfile);
// Store the number of planes, then allocate memory to hold them
// Načti počet dělicích ploch a alokuj pro ně paměť
m_iNumOfPlanes = t_bsp_lump[LUMPS_PLANES].i_length / sizeof(TBspPlane);
m_ptBspPlane = new TBspPlane [m_iNumOfPlanes];
// Seek to the planes lump in the file, then read them into m_ptBspPlane
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty dělicích ploch a tyto hodnoty načti
fseek(pfile, t_bsp_lump[LUMPS_PLANES].i_offset, SEEK_SET);
fread(m_ptBspPlane, m_iNumOfPlanes, sizeof(TBspPlane), pfile);
// Go through every plane and convert it's normal to the Y-axis being up
// Projdi všechny dělicí plochy a prohoď hodnoty Y na Z všech obálek listů.
// U Z hodnoty prohoď znaménko
for(i = 0; i < m_iNumOfPlanes; i++)
{
float f_temp = m_ptBspPlane[i].pf_normal[1];
m_ptBspPlane[i].pf_normal[1] = m_ptBspPlane[i].pf_normal[2];
m_ptBspPlane[i].pf_normal[2] = -f_temp;
}
// Seek to the position in the file that holds the visibility lump
// Najdi podle počtu bytů (t_bsp_lump[]) od 0 pozice (SEEK_SET(začátek souboru)) v BSP souboru
// uložené hodnoty bitových informací viditelnosti a tyto hodnoty načti
fseek(pfile, t_bsp_lump[LUMPS_VIS_DATA].i_offset, SEEK_SET);
// Check if there is any visibility information first
// Pokud existuje nějaká bitová informace viditelnosti
if(t_bsp_lump[LUMPS_VIS_DATA].i_length)
{
// Read in the number of vectors and each vector's size
// Načti počet klastrů a jejich bytovou(1byte = 8bitů) velikosti
fread(&(m_tBspVisData.i_num_of_clusters), 1, sizeof(int), pfile);
fread(&(m_tBspVisData.i_bytes_per_cluster), 1, sizeof(int), pfile);
// Allocate the memory for the cluster bitsets
// Alokuj paměť pro bitové hodnoty klastrů
int i_size = m_tBspVisData.i_num_of_clusters * m_tBspVisData.i_bytes_per_cluster;
m_tBspVisData.pby_cluster_bitsets = new byte [i_size];
// Read in the all the visibility bitsets for each cluster
// Načti všechny bitové hodnoty klastrů pro detekci viditelnosti
fread(m_tBspVisData.pby_cluster_bitsets, 1, sizeof(byte) * i_size, pfile);
}
// Otherwise, we don't have any visibility data (prevents a crash)
else // Pokud neexistují žádné bitové informace viditelnosti, tak
m_tBspVisData.pby_cluster_bitsets = NULL; // Nastav bitové hodnoty klastrů na NULL
// Close the file
// Zavři soubor
fclose(pfile);
// Here we allocate enough bits to store all the faces for our bitset
// Alokuj dostatečné množství bitů pro všechny plošky, bitové nastavení klastrů
m_BitsetFacesDrawn.Resize(m_iNumOfFaces);
// Return a success
// Pokud jsi došel až sem, tak vrať funkci true
return true;
}
|
Funkce LoadBSP() volá pomocnou funkci CreateLightmapTexture() třídy K_CTexture,
která generuje lightmap textury, z načtených RGB hodnot pppby_image_bits a velikosti textury
128x128. Tato funkce volá funkci ChangeGamma() která změní jas podle načtené hodnoty
g_fGamma
|
|
// This creates a texture map from the light map image bits
// Vytvoř lightmap texturu
void K_CTexture::CreateLightmapTexture(UINT &ui_texture, byte *pby_image_bits, int i_width, int i_height)
{
// Generate a texture with the associative texture ID stored in the array
// Vygeneruj specifické jméno textury
glGenTextures(1, &ui_texture);
// This sets the alignment requirements for the start of each pixel row in memory.
// Nastav bytové zarovnání požadované pro začátek každé pixelové řady v paměti
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Bind the texture to the texture arrays index and init the texture
// Zapni vytváření textury podle specifického jména textury
glBindTexture(GL_TEXTURE_2D, ui_texture);
// Change the lightmap gamma values by our desired gamma
// Změň gamma hodnotu osvětlení scény podle hodnoty m_fGamma
ChangeGamma(pby_image_bits, i_width * i_height * 3, g_fGamma);
// Build Mipmaps (builds different versions of the picture for distances - looks better)
// Vytvoř mipmapy
// Mipmapa je řada po sobě jdoucích stejných obrázků různé velikosti,
// podle vzdálenosti od kamery se zobrazuje určitá velikost obrázku.
// Tím se docílí kvalitnějšího zobrazení scény
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, i_width, i_height, GL_RGB, GL_UNSIGNED_BYTE, pby_image_bits);
// Assign the mip map levels
// Vytvoř mipmat texturu podle těchto hodnot
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
//----------------------------------------------------------------------------------------------------//
// This manually changes the gamma of an image
// Změň gamma hodnotu osvětlení scény podle hodnoty i_factor
void K_CTexture::ChangeGamma(byte *pby_image, int i_size, float i_factor)
{
// Go through every pixel in the lightmap
// Projdi všechny pixely lightmapy
for(int i = 0; i < i_size / 3; i++, pby_image += 3)
{
float f_scale = 1.0f; // Měřítko nastav na 1
float f_temp = 0.0f; // Odkádací zásobník nastav na 0
float f_r = 0, f_g = 0, f_b = 0; // Barevné kanály nastav na 0
// Extract the current RGB values
// Zjisti RGB hodnoty lightmapy
f_r = (float) pby_image[0];
f_g = (float) pby_image[1];
f_b = (float) pby_image[2];
// Multiply the factor by the RGB values, while keeping it to a 255 ratio
// Vynásob faktor hodnotami RGB, a vyděl 255 aby se dali identifikovat nejvyšší hodnoty
f_r = f_r * i_factor / 255.0f;
f_g = f_g * i_factor / 255.0f;
f_b = f_b * i_factor / 255.0f;
// Check if the the values went past the highest value
// Pokud jsou RGB hodnoty vyšší než 1
// a zároveň zásobník (1 / RGB) je menší než měřítko,
// tak předej hodnoty zásobníku hodnotám měřítka.
if(f_r > 1.0f && (f_temp = (1.0f / f_r)) < f_scale) f_scale = f_temp;
if(f_g > 1.0f && (f_temp = (1.0f / f_g)) < f_scale) f_scale = f_temp;
if(f_b > 1.0f && (f_temp = (1.0f / f_b)) < f_scale) f_scale = f_temp;
// Get the scale for this pixel and multiply it by our pixel values
// Získej správné měřítko pro pixel násobením 255
// a všechny hodnoty RGB vynásob hodnotou měřítka
f_scale *= 255.0f;
f_r *= f_scale;
f_g *= f_scale;
f_b *= f_scale;
// Assign the new gamma'nized RGB values to our image
// Přidej nové gamma hodnoty RGB do obrazové informace
// ze které budeme vytvářet konečnou lightmap texturu
pby_image[0] = (byte) f_r;
pby_image[1] = (byte) f_g;
pby_image[2] = (byte) f_b;
}
}
|
Funkce LoadBSP() také volá pomocnou funkci m_BitsetFacesDrawn.Resize(m_iNumOfFaces);
pro alokaci dostatečného množství bitů pro všechny plošky, bitové nastavení klastrů
|
|
// This resizes our bitset to a size so each face has a bit associated with it
// Alokuj dostatečné množství bitů pro všechny plošky pro bitové nastavení klastrů
void Resize(int i_count)
{
// Get the size of integers we need
// Získej velikost v hodnotě integer
m_iSize = i_count / 32 + 1;
// Make sure we haven't already allocated memory for the bits
// Vymaž alokovanou paměť bitu
// Jestliže je zaplněna paměť bitových dat, tak
if(m_puiBits)
{
delete m_puiBits; // Vymaž bit
m_puiBits = 0; // Nastav ho na 0
}
// Allocate the bits and initialize them
// Alokuj paměť pro nová bitová data a nastav jejich velikost podle hodnoty m_isize
m_puiBits = new unsigned int[m_iSize];
ClearAll(); // Nastav všechny bity na 0
}
// This initializes the bits to 0
// Nastav všechny bity na 0
void ClearAll()
{
memset(m_puiBits, 0, sizeof(unsigned int) * m_iSize);
}
|
Nyní když známe všechny potřebné hodnoty začneme vykreslovat Quake III BSP scénu.
Vykreslování scény provádí funkce RenderLevel(), která je volána funkcí
DrawScene() třídy K_CScene
|
|
// Vykresli scénu
void K_CScene::DrawScene()
{
...
...
...
// Render the level to the screen
// Vykresli QuakeIII scénu
m_pQuake3Bsp->RenderLevel(g_pfCameraPosition);
...
...
...
}
// Vykresli QuakeIII BSP scénu
void K_CQuake3Bsp::RenderLevel(const float *pf_camera_position)
{
// Give OpenGL our vertices to use for vertex arrays
// Získej OpenGL ukazatele vrcholů
glVertexPointer(3, GL_FLOAT, sizeof(TBspVertex), &(m_ptBspVertex[0].pf_position));
// If we want to render the multitexturing
// Jestliže je zapnut multitexturing
if(g_bMultitexture)
{
// Assign the first texture pass to point to the normal texture coordinates
// Nastav první texturový ukazatel pro texturovou koordinaci
glClientActiveTextureARB(GL_TEXTURE0_ARB);
}
// If we want to render the textures
// Jestliže používáme texturování
if(g_bTextures)
{
// Nastav texturovou koordinaci první textury
glTexCoordPointer(2, GL_FLOAT, sizeof(TBspVertex), &(m_ptBspVertex[0].pf_texture_coord));
}
// If we want to render the multitexturing
// Jestliže je zapnut multitexturing
if(g_bMultitexture)
{
// Assign the second texture pass to point to the light map texture coordinates
// Nastav druhý texturový ukazatel pro texturovou koordinaci
glClientActiveTextureARB(GL_TEXTURE1_ARB);
}
// If we want to view the lightmap textures
// Jestliže jsou zapnuty lightmapy
if(g_bLightmaps)
{
// Nastav texturovou koordinaci druhé textury
glTexCoordPointer(2, GL_FLOAT, sizeof(TBspVertex), &(m_ptBspVertex[0].pf_lightmap_coord));
}
// Set our vertex array client states for vertices and texture coordinates
// Nastav pole vrcholů a texturových koordinací
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
// If we want to view the textures
// Jestliže používáme texturování
if(g_bTextures)
{
// If we want to render the multitexturing
// Jestliže je zapnut multitexturing
if(g_bMultitexture)
{
// Turn on texture arrays for the first pass
// Nastav první texturový ukazatel pro texturovou koordinaci
glClientActiveTextureARB(GL_TEXTURE0_ARB);
}
// Nastav pole texturových koordinací první textury
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
// If we want to view the lightmap textures
// Jestliže jsou zapnuty lightmapy
if(g_bLightmaps)
{
// If we want to render the multitexturing
// Jestliže je zapnut multitexturing
if(g_bMultitexture)
{
// Turn on texture arrays for the second lightmap pass
// Nastav druhý texturový ukazatel pro texturovou koordinaci
glClientActiveTextureARB(GL_TEXTURE1_ARB);
}
// Nastav pole texturových koordinací druhé textury
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
// Reset our bitset so all the slots are zero.
// Vyčisti paměť ve které jsou uložena bitová data vykreslených plošek
m_BitsetFacesDrawn.ClearAll();
// *English*
// In this new revision of RenderLevel(), we do things a bit differently.
// Instead of looping through all the faces, we now want to loop through
// all of the leafs. Each leaf stores a list of faces assign to it.
// We call FindLeaf() to find the current leaf index that our camera is
// in. This leaf will then give us the cluster that the camera is in. The
// cluster is then used to test visibility between our current cluster
// and other leaf clusters. If another leaf's cluster is visible from our
// current cluster, the leaf's bounding box is checked against our frustum.
// Assuming the bounding box is inside of our frustum, we draw all the faces
// stored in that leaf.
//
// *Česky*
// Nyní budeme procházet všechny listy BSP stromu. Ve všech listech jsou uloženy plošky.
// Pomocí funkce FindLeaf() najdeme aktuální list plošek, ve kterém se nachází kamera.
// Tento list má svojí specifickou bitovou informaci tzv. klastr (cluster).
// Pomocí klastrů zjistíme viditelnost plošek okolo listu, ve kterém se nachází kamera.
// Pomocí obálek (bounding box) listů zjistíme viditelné plošky v zorném úhlu kamery.
// Nakonec vykreslíme všechny plošky viditelných listů.
// Grab the leaf index that our camera is in
// Získej index listu, ve kterém se nachází kamera
int i_leaf_index = FindLeaf(pf_camera_position);
// Grab the cluster that is assigned to the leaf
// Získej podle indexu klastr listu, ve kterém se nachází kamera
int i_camera_in_cluster = m_ptBspLeaf[i_leaf_index].i_cluster;
// Initialize our counter variables (start at the last leaf and work down)
int i = m_iNumOfLeafs; // Počet listů předej hodnotě i
m_iVisibleFaces = 0; // Vymaž vyditelné plošky
// Since we are using frustum culling to only draw the visible BSP leafs,
// we need to calculate the frustum every frame. This needs to happen
// right after we position our camera. Now the frustum planes can be defined.
// Získej hodnoty zorného pole kamery z projekční a modelové matice scény.
// Podle hodnot zorného pole pak budeme určovat, které BSP listy jsou viditelné
// a které ne. Tento výpočet musíme provádět v každém cyklu překreslování scény.
m_Frustum.CalculateFrustum();
// Go through all the leafs and check their visibility
// Projdí všechny listy a zjisti jejich viditelnost
while(i--)
{
// Get the current leaf that is to be tested for visibility from our camera's leaf
// Získej aktuální list, který budeme používat k detekci viditelnosti
TBspLeaf *p_bsp_leaf = &(m_ptBspLeaf[i]);
// If the current leaf can't be seen from our cluster, go to the next leaf
// Jestliže aktuální klastr listu neni viditelný z klastru, ve kterém je kamera, tak
if(!IsClusterVisible(i_camera_in_cluster, p_bsp_leaf->i_cluster))
continuet; // Se vrať na začátek cyklu na další list
// If the current leaf is not in the camera's frustum, go to the next leaf
// Jestliže aktuální list není v zorném úhlu kamery, tak
if(!m_Frustum.BoxInFrustum(p_bsp_leaf->pi_min_box[0], p_bsp_leaf->pi_min_box[1], p_bsp_leaf->pi_min_box[2],
p_bsp_leaf->pi_max_box[0], p_bsp_leaf->pi_max_box[1], p_bsp_leaf->pi_max_box[2]))
continue; // Se vrať na začátek cyklu na další list
// If we get here, the leaf we are testing must be visible in our camera's view.
// Get the number of faces that this leaf is in charge of.
// Získej počet plošek v listu
int i_num_of_leaf_faces = p_bsp_leaf->i_num_of_leaf_faces;
// Loop through and render all of the faces in this leaf
// Projdi všechny plošky listu
while(i_num_of_leaf_faces--)
{
// Grab the current face index from our leaf faces array
// Získej index plošky
int i_face_index = m_piLeafFaces[p_bsp_leaf->i_leaf_face + i_num_of_leaf_faces];
// Before drawing this face, make sure it's a normal polygon
// Před vykresloním plošky se ujisti, zda je ploška trojúhelník, nebo jiný polygon
if(m_ptBspFace[i_face_index].i_type != FACE_POLYGON)
continue; // Pokud ploška není trojúhelník, tak se vrať na začátek cyklu na další plošku
// Since many faces are duplicated in other leafs, we need to
// make sure this face already hasn't been drawn.
// Jestliže ploška, která má být vykreslena, nebyla již vykreslena, tak
if(!m_BitsetFacesDrawn.On(i_face_index))
{
// Increase the rendered face count to display for fun
// Přidej 1 k počtu viditelných plošek
m_iVisibleFaces++;
// Set this face as drawn and render it
// Nastav, plošku jako vykreslenou
m_BitsetFacesDrawn.Set(i_face_index);
// Vykresli plošku podle hodnoty indexu
RenderFace(i_face_index);
}
}
}
}
|
Funkce RenderLevel() volá další pomocné funkce. První je funkce ClearAll(), která nastavuje všechny bitové
hodnoty viditelnosti plošek na 0. Funkce je třídy K_CBitset
|
|
// This initializes the bits to 0
// Nastav všechny bity na 0
void ClearAll()
{
memset(m_puiBits, 0, sizeof(unsigned int) * m_iSize);
}
|
Druhá pomocná funkce FindLeaf()
získá index listu, ve kterém se nachází kamera.
Funkce najde list BSP stromu, ve kterém se nachází kamera.
Vezme se pozice kamery a v cyklu se prochází indexi i uzlů listů.
Začíná se na od indexu 0 (kořen BSP stromu) a
pokračuje se ve vyhledávání do té doby, dokud jeden z indexů nemá zápornou hodnotu.
Detekce se provádí pomocí dělicích ploch,
které rozdělují uzel na dva listy.
Přední list má kladný index, zadní list má záporný index.
Pomocí obecné rovnice roviny (Ax + By + Cz + D = 0) se vypočítá vzdálenost kamery od dělicí plochy.
Pokud vzdálenost je kladná, nebo nulová, tak se počítá další dělicí plocha.
Pokud je vzdálenost záporná, nachází se kamera v těsné blízkosti dělicí plochy
v zadním listu se zápornou hodnotou indexu. Zápornou hodnotu indexu listu převedem na kladnou
použitím binárního operatoru ~, nebo -(i + 1), abychom ho mohli použít pro detekci listů,
které se nacházejí v zorném úhlu kamery, funkce IsClusterVisible() viz. níže.
|
|
inline int K_CQuake3Bsp::FindLeaf(const float *pf_camera_position)
{
int i = 0; // Návratová hodnota indexu listu
float f_distance = 0.0f; // Vzdálenost od plochy, kdy dochází k detekci
// Continue looping until we find a negative index
// Cykluj, dokud nebude návratová hodnota indexu listu záporná
while(i >= 0)
{
// Get the current node, then find the slitter plane from that
// node's plane index. Notice that we use a constant reference
// to store the plane and node so we get some optimization.
// Získej aktuální uzek a dělicí plochu s indexem tohoto uzlu.
const TBspNode& node = m_ptBspNode[i]; // Získej uzel
const TBspPlane& plane = m_ptBspPlane[node.i_plane]; // Získej dělicí plochu tohoto uzlu
// Use the Plane Equation (Ax + By + Cz + D = 0) to find if the
// camera is in front of or behind the current splitter plane.
// Použij obecnou rovnici roviny (Ax + By + Cz + D = 0) pro výpočet,
// zda je kamera před, nebo za dělicí plochou.
f_distance = plane.pf_normal[0] * pf_camera_position[0] +
plane.pf_normal[1] * pf_camera_position[1] +
plane.pf_normal[2] * pf_camera_position[2] - plane.f_distance;
// If the camera is in front of the plane
// Pokud je výsledek rovnice větší nebo rovno 0,
// tak je kamera před dělicí rovinou, je tedy v předním listu
if(f_distance >= 0)
{
// Assign the current node to the node in front of itself
// Ulož přední hodnotu uzlu do indexu listu
i = node.i_front;
}
// Else if the camera is behind the plane
// Pokud je výsledek rovnice menší než 0,
// tak je kamera za dělicí rovinou, je tedy v zadním listu
else
{
// Assign the current node to the node behind itself
// Ulož zadní hodnotu uzlu do indexu listu
i = node.i_back;
}
}
// Return the leaf index (same thing as saying: return -(i + 1)).
// Pokud dojdeš sem, tak jeden z indexů listu měl zápornou hodnotu.
// V tomto listu se tudíž kamera nachází.
// Musíme však přehodit zápornou hodnotu na kladnou pomocí binárního
// operátoru ~, který převádí rychleji než -(i + 1))
return ~i; // Binary operation
}
|
Třetí pomocná funkce CalculateFrustum() třídy K_CFrustum
získá hodnoty zorného pole kamery z projekční a modelové matice scény.
Podle hodnot zorného pole pak budeme určovat, které BSP listy jsou viditelné
a které ne. Funkce CalculateFrustum() používá pomocné enumerátory FrustumSide
a PlaneData, které definují zorné pole kamery a normálový vektor ploch stran zorného pole kamery.
Potom volá pomocnou funkci NormalizePlane(),
která normalizuje vektor plochy dané strany zorného pole kamery.
|
|
//----------------------------------------------------------------------------------------------------//
// We create an enum of the sides so we don't have to call each side 0 or 1.
// This way it makes it more understandable and readable when dealing with frustum sides.
// Definuj hodnoty šesti stran pomyslného komolého jehlanu zorného pole kamery.
enum FrustumSide
{
// The RIGHT side of the frustum
// Pravá strana zorného pole kamery
RIGHT,
// The LEFT side of the frustum
// Levá strana zorného pole kamery
LEFT,
// The BOTTOM side of the frustum
// Spodní strana zorného pole kamery
BOTTOM,
// The TOP side of the frustum
// Horní strana zorného pole kamery
TOP,
// The BACK side of the frustum
// Zadní strana zorného pole kamery
BACK,
// The FRONT side of the frustum
// Přední strana zorného pole kamery
FRONT
};
//----------------------------------------------------------------------------------------------------//
// Like above, instead of saying a number for the ABC and D of the plane, we
// want to be more descriptive.
// Definuj hodnoty (A B C) normálového vektoru plochy stran zorného pole kamera a ( D ) vzdálenost
// bodu od plochy kdy dochází k detekci.
enum PlaneData
{
// The X value of the plane's normal
// X hodnota normály plochy
A,
// The Y value of the plane's normal
// Y hodnota normály plochy
B,
// The Z value of the plane's normal
// Z hodnota normály plochy
C,
// The distance the plane is from the origin
// Vzdálenost bodu od plochy kdy dochází k detekci
D
};
//----------------------------------------------------------------------------------------------------//
// This normalizes a plane (A side) from a given frustum.
// Normalizuj vektor plochy dané strany zorného pole kamery
void K_CFrustum::NormalizePlane(float ppf_frustum[6][4], int i_side)
{
// Here we calculate the magnitude of the normal to the plane (point A B C)
// Remember that (A, B, C) is that same thing as the normal's (X, Y, Z).
// To calculate magnitude you use the equation: magnitude = sqrt( x^2 + y^2 + z^2)
//
// Vypočítej velikost normálového vektoru plochy podle hodnot normálového vektoru ABC
// ABC reprezentují hodnoty XYZ.
// Rovnice pro výpočet velikosti normálového vektoru vypadá takto:
// velikost = sqrt( x^2 + y^2 + z^2)
float i_magnitude = (float)sqrt( ppf_frustum[i_side][A] * ppf_frustum[i_side][A] +
ppf_frustum[i_side][B] * ppf_frustum[i_side][B] +
ppf_frustum[i_side][C] * ppf_frustum[i_side][C] );
// Then we divide the plane's values by it's magnitude.
// This makes it easier to work with.
// Normalizuj vektor vydělením zorného pole kamery "ppf_frustum" velikotí vektoru "i_ magnitude"
ppf_frustum[i_side][A] /= i_magnitude;
ppf_frustum[i_side][B] /= i_magnitude;
ppf_frustum[i_side][C] /= i_magnitude;
ppf_frustum[i_side][D] /= i_magnitude;
}
//----------------------------------------------------------------------------------------------------//
// This extracts our frustum from the projection and modelview matrix.
// Získej hodnoty zorného pole kamery z projekční a modelové matice scény
void K_CFrustum::CalculateFrustum()
{
// This will hold our projection matrix
// Schránka pro proječní matici
float pf_proj[16];
// This will hold our modelview matrix
// Schránka pro modelovou matici
float pf_modl[16];
// This will hold the clipping planes
// Schránka pro ořezávací matici ploch
float pf_clip[16];
// glGetFloatv() is used to extract information about our OpenGL world.
// Pomocí funkce glGetFloatv() získáme informace o naší OpenGL scéně.
// Below, we pass in GL_PROJECTION_MATRIX to abstract our projection matrix.
// It then stores the matrix into an array of [16].
// Získej hodnoty proječní matice GL_PROJECTION_MATRIX a ulož je
// do schránky proječní matice pf_proj[16]
glGetFloatv( GL_PROJECTION_MATRIX, pf_proj );
// By passing in GL_MODELVIEW_MATRIX, we can abstract our model view matrix.
// This also stores it in an array of [16].
// Získej hodnoty modelové matice GL_MODELVIEW_MATRIX a ulož je
// do schránky modelové matice pf_modl[16]
glGetFloatv( GL_MODELVIEW_MATRIX, pf_modl );
// Now that we have our modelview and projection matrix, if we combine these 2 matrices,
// it will give us our clipping planes. To combine 2 matrices, we multiply them.
// Vynásobením projekční a modelové matice získej ořezávací matici ploch
pf_clip[ 0] = pf_modl[ 0] * pf_proj[ 0] + pf_modl[ 1] * pf_proj[ 4] + pf_modl[ 2] * pf_proj[ 8] + pf_modl[ 3] * pf_proj[12];
pf_clip[ 1] = pf_modl[ 0] * pf_proj[ 1] + pf_modl[ 1] * pf_proj[ 5] + pf_modl[ 2] * pf_proj[ 9] + pf_modl[ 3] * pf_proj[13];
pf_clip[ 2] = pf_modl[ 0] * pf_proj[ 2] + pf_modl[ 1] * pf_proj[ 6] + pf_modl[ 2] * pf_proj[10] + pf_modl[ 3] * pf_proj[14];
pf_clip[ 3] = pf_modl[ 0] * pf_proj[ 3] + pf_modl[ 1] * pf_proj[ 7] + pf_modl[ 2] * pf_proj[11] + pf_modl[ 3] * pf_proj[15];
pf_clip[ 4] = pf_modl[ 4] * pf_proj[ 0] + pf_modl[ 5] * pf_proj[ 4] + pf_modl[ 6] * pf_proj[ 8] + pf_modl[ 7] * pf_proj[12];
pf_clip[ 5] = pf_modl[ 4] * pf_proj[ 1] + pf_modl[ 5] * pf_proj[ 5] + pf_modl[ 6] * pf_proj[ 9] + pf_modl[ 7] * pf_proj[13];
pf_clip[ 6] = pf_modl[ 4] * pf_proj[ 2] + pf_modl[ 5] * pf_proj[ 6] + pf_modl[ 6] * pf_proj[10] + pf_modl[ 7] * pf_proj[14];
pf_clip[ 7] = pf_modl[ 4] * pf_proj[ 3] + pf_modl[ 5] * pf_proj[ 7] + pf_modl[ 6] * pf_proj[11] + pf_modl[ 7] * pf_proj[15];
pf_clip[ 8] = pf_modl[ 8] * pf_proj[ 0] + pf_modl[ 9] * pf_proj[ 4] + pf_modl[10] * pf_proj[ 8] + pf_modl[11] * pf_proj[12];
pf_clip[ 9] = pf_modl[ 8] * pf_proj[ 1] + pf_modl[ 9] * pf_proj[ 5] + pf_modl[10] * pf_proj[ 9] + pf_modl[11] * pf_proj[13];
pf_clip[10] = pf_modl[ 8] * pf_proj[ 2] + pf_modl[ 9] * pf_proj[ 6] + pf_modl[10] * pf_proj[10] + pf_modl[11] * pf_proj[14];
pf_clip[11] = pf_modl[ 8] * pf_proj[ 3] + pf_modl[ 9] * pf_proj[ 7] + pf_modl[10] * pf_proj[11] + pf_modl[11] * pf_proj[15];
pf_clip[12] = pf_modl[12] * pf_proj[ 0] + pf_modl[13] * pf_proj[ 4] + pf_modl[14] * pf_proj[ 8] + pf_modl[15] * pf_proj[12];
pf_clip[13] = pf_modl[12] * pf_proj[ 1] + pf_modl[13] * pf_proj[ 5] + pf_modl[14] * pf_proj[ 9] + pf_modl[15] * pf_proj[13];
pf_clip[14] = pf_modl[12] * pf_proj[ 2] + pf_modl[13] * pf_proj[ 6] + pf_modl[14] * pf_proj[10] + pf_modl[15] * pf_proj[14];
pf_clip[15] = pf_modl[12] * pf_proj[ 3] + pf_modl[13] * pf_proj[ 7] + pf_modl[14] * pf_proj[11] + pf_modl[15] * pf_proj[15];
// Now we actually want to get the sides of the frustum. To do this we take
// the clipping planes we received above and extract the sides from them.
// Nyní získáme strany zorného pole kamery. Získáme je z hodnot vypočítané
// matice ořezávání ploch.
// This will extract the RIGHT side of the frustum
// Získej pravou stranu zorného pole kamery
m_ppfFrustum[RIGHT][A] = pf_clip[ 3] - pf_clip[ 0];
m_ppfFrustum[RIGHT][B] = pf_clip[ 7] - pf_clip[ 4];
m_ppfFrustum[RIGHT][C] = pf_clip[11] - pf_clip[ 8];
m_ppfFrustum[RIGHT][D] = pf_clip[15] - pf_clip[12];
// Now that we have a normal (A,B,C) and a distance (D) to the plane,
// we want to normalize that normal and distance.
// Nyní známe normálový vektor (A,B,C) a vzdálenost (D),
// který musíme normalizovat.
// Normalize the RIGHT side
// Normalizuj pravou stranu zorného pole kamery
NormalizePlane(m_ppfFrustum, RIGHT);
// This will extract the LEFT side of the frustum
// Získej levou stranu zorného pole kamery
m_ppfFrustum[LEFT][A] = pf_clip[ 3] + pf_clip[ 0];
m_ppfFrustum[LEFT][B] = pf_clip[ 7] + pf_clip[ 4];
m_ppfFrustum[LEFT][C] = pf_clip[11] + pf_clip[ 8];
m_ppfFrustum[LEFT][D] = pf_clip[15] + pf_clip[12];
// Normalize the LEFT side
// Normalizuj levou stranu zorného pole kamery
NormalizePlane(m_ppfFrustum, LEFT);
// This will extract the BOTTOM side of the frustum
// Získej spodní stranu zorného pole kamery
m_ppfFrustum[BOTTOM][A] = pf_clip[ 3] + pf_clip[ 1];
m_ppfFrustum[BOTTOM][B] = pf_clip[ 7] + pf_clip[ 5];
m_ppfFrustum[BOTTOM][C] = pf_clip[11] + pf_clip[ 9];
m_ppfFrustum[BOTTOM][D] = pf_clip[15] + pf_clip[13];
// Normalize the BOTTOM side
// Normalizuj spodní stranu zorného pole kamery
NormalizePlane(m_ppfFrustum, BOTTOM);
// This will extract the TOP side of the frustum
// Získej horní stranu zorného pole kamery
m_ppfFrustum[TOP][A] = pf_clip[ 3] - pf_clip[ 1];
m_ppfFrustum[TOP][B] = pf_clip[ 7] - pf_clip[ 5];
m_ppfFrustum[TOP][C] = pf_clip[11] - pf_clip[ 9];
m_ppfFrustum[TOP][D] = pf_clip[15] - pf_clip[13];
// Normalize the TOP side
// Normalizuj horní stranu zorného pole kamery
NormalizePlane(m_ppfFrustum, TOP);
// This will extract the BACK side of the frustum
// Získej zadní stranu zorného pole kamery
m_ppfFrustum[BACK][A] = pf_clip[ 3] - pf_clip[ 2];
m_ppfFrustum[BACK][B] = pf_clip[ 7] - pf_clip[ 6];
m_ppfFrustum[BACK][C] = pf_clip[11] - pf_clip[10];
m_ppfFrustum[BACK][D] = pf_clip[15] - pf_clip[14];
// Normalize the BACK side
// Normalizuj zadní stranu zorného pole kamery
NormalizePlane(m_ppfFrustum, BACK);
// This will extract the FRONT side of the frustum
// Získej pření stranu zorného pole kamery
m_ppfFrustum[FRONT][A] = pf_clip[ 3] + pf_clip[ 2];
m_ppfFrustum[FRONT][B] = pf_clip[ 7] + pf_clip[ 6];
m_ppfFrustum[FRONT][C] = pf_clip[11] + pf_clip[10];
m_ppfFrustum[FRONT][D] = pf_clip[15] + pf_clip[14];
// Normalize the FRONT side
// Normalizuj přední stranu zorného pole kamery
NormalizePlane(m_ppfFrustum, FRONT);
}
//----------------------------------------------------------------------------------------------------//
|
Čtvrtá pomocná funkce IsClusterVisible()
testuje, které listy BSP scény se nacházejí kolem kamery podle klastrových (cluster) informací.
Každý list obsahuje bitové (1 = viditelný, 0 = neviditelný) informace (cluster). Kolik je uzlů ve scéně,
tolik bitů má hlavní uzel ((node)kořen stromu).
Pokud má například scéna 10 uzlů (což je velmi malá scéna),
tak hlavní uzel ((node)kořen stromu) bude mít v clusteru uloženo 10bitů informací,
další dva uzly listu hlavniho uzlu budou mít 9 bitů atd.
Bitové operace se používají, protože rychleji pracují s uloženými daty.
|
|
// Testuj, které listy BSP scény se nacházejí kolem kamery podle klastrových (cluster) informací
inline int K_CQuake3Bsp::IsClusterVisible(int i_camera_in_cluster, int i_test_cluster)
{
// Make sure we have valid memory and that the current cluster is > 0.
// If we don't have any memory or a negative cluster, return a visibility (1).
// Ujisti se jestli jsou klastrová data v paměťi a že klastr ve kterém je kamera je > 0
// Jestliže neexistují klastrová data, nebo klastr ve kterém je kamera je < 0,
// tak vrať funkci 1. Tím tento klastr označíme jako viditelný.
// Pokud by se označil jako neviditelný, mohlo by se stát, že by
// ve scéně mohly vzniknout díry.
if(!m_tBspVisData.pby_cluster_bitsets || i_camera_in_cluster < 0) return 1;
// Use binary math to get the 8 bit visibility set for the current cluster
// Použij binární operaci pro získání 8 bitové informace o viditelnosti klastru ve kterém je kamera
// Bitová operace (test >> 3) posune bity v "test" o 3 pozice doprava,
// čímž provede to samé (avšak rychleji), jako (test / 8)
byte by_vis_set = m_tBspVisData.pby_cluster_bitsets[(i_camera_in_cluster * m_tBspVisData.i_bytes_per_cluster) + (i_test_cluster >> 3)];
// Now that we have our vector (bitset), do some bit shifting to find if
// the "test" cluster is visible from the "current" cluster, according to the bitset.
// Porovnej pomocí bitového součinu "&" bitové hodnoty klastru ve kterém je kamera a testovaného klastru.
// Bitový součin "&" nám vrací 1, pokud jsou obě hodnoty stejné, a 0 pokud jsou rozdílné
// Bitový posun doleva "1 <<" vynásobí jedničkou výslednou hodnotu bitového součinu ((test) & 7))
// Pokud bude hodnota např. 1, vynásobí se (1 * 2), pokud bude hodnota např. 4, vynásobí (1 * 16) atd..
int result = by_vis_set & (1 << ((i_test_cluster) & 7));
// Return the result ( either 1 (visible) or 0 (not visible) )
// Vrať funkci výsledek bitových operací (výsledek 1 = viditelný klastr, výsledek 0 = neviditelný klastr)
return ( result );
}
|
Pátá pomocná funkce BoxInFrustum()
detekuje, jestli je detekční obálka listu (kvádr) v zorném poli kamery.
Vstupní body funkce jsou dva. První bod obsahuje minimální xyz hodnoty obálky, druhý maximální.
Pomocí těchto bodů se vytvoří detekční obálka (kvádr) listu.
Pokud je alespoň jeden bod obálky listu uvnitř zorného pole kamery, tak funkce vrací true.
Pokud všechny body listu jsou mimo zorné pole kamery, tak funkce vrací false.
Funkce je třídy K_CFrustum a používá enumerátor normálového vektoru plochy stran zorného pole kamera
enum PlaneData{A, B, C, D}
|
|
// Detekuj, jestli je detekční obálka listu (kvádr) v zorném poli kamery.
bool K_CFrustum::BoxInFrustum( float f_min_x, float f_min_y, float f_min_z, float f_max_x, float f_max_y, float f_max_z)
{
// Go through all of the corners of the box and check then again each plane
// in the frustum. If all of them are behind one of the planes, then it most
// like is not in the frustum.
//
// Projdi všechny strany zorného pole kamery a detekuj všechny body detekční obálky listu.
for(int i = 0; i < 6; i++ )
{
// Pokud jeden z bodů detekční obálky listu je uvnitř zorného pole (před detekovanou stranou zorného pole kamery),
// tak skoč (continue) na detekci další strany zorného pole kamery
if(m_ppfFrustum[i][A] * f_min_x + m_ppfFrustum[i][B] * f_min_y + m_ppfFrustum[i][C] * f_min_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_max_x + m_ppfFrustum[i][B] * f_min_y + m_ppfFrustum[i][C] * f_min_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_min_x + m_ppfFrustum[i][B] * f_max_y + m_ppfFrustum[i][C] * f_min_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_max_x + m_ppfFrustum[i][B] * f_max_y + m_ppfFrustum[i][C] * f_min_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_min_x + m_ppfFrustum[i][B] * f_min_y + m_ppfFrustum[i][C] * f_max_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_max_x + m_ppfFrustum[i][B] * f_min_y + m_ppfFrustum[i][C] * f_max_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_min_x + m_ppfFrustum[i][B] * f_max_y + m_ppfFrustum[i][C] * f_max_z + m_ppfFrustum[i][D] > 0) continue;
if(m_ppfFrustum[i][A] * f_max_x + m_ppfFrustum[i][B] * f_max_y + m_ppfFrustum[i][C] * f_max_z + m_ppfFrustum[i][D] > 0) continue;
// If we get here, it isn't in the frustum
// Pokud všechny body jsou mimo zorné pole kamery, tak vrať funkci false
return false;
}
// Return a true for the box being inside of the frustum
// Jinak vrať funkci true
// Detekční obálka listu je v zorném poli kamery
return true;
}
|
Šestá pomocná funkce On() třídy K_CBitset detekuje podle indexu
plošky, které plošky byly již vykresleny.
|
|
// This returns if the desired bit slot is a 1 or a 0
// Funkce zjišťuje jestli ploška, která má být vykreslena,
// nebyla již vykreslena
// Vrať funkci bit 0, nebo 1
int On(int i)
{
// Použij bitové posuny
return m_puiBits[i >> 5] & (1 << (i & 31 ));
}
|
Sedmá pomocná funkce Set() třídy K_CBitset nastavuje plošku jako vykreslenou.
|
|
// This does the binary math to set the desired bit
// Nastaví, plošku jako vykreslenou
// Nastav bit na 1
void Set(int i)
{
// Použij bitové posuny
m_puiBits[i >> 5] |= (1 << (i & 31));
}
|
Osmá a poslední pomocná funkce RenderFace() vykreslí jen ty plošky, které
prošly všemi testy.
|
|
// Vykresli plošky odpovídajících indexů, které prošli detekcí vyditelnosti
inline void K_CQuake3Bsp::RenderFace(int i_face_index)
{
// Here we grab the face from the index passed in
// Získej hodnoty BSP plošky aktuálního indexu
TBspFace *p_bsp_face = &m_ptBspFace[i_face_index];
// If we want to render the textures
// Jestliže používáme texturování
if(g_bTextures)
{
// If we want to render the multitexturing
// Jestliže je zapnut multitexturing
if(g_bMultitexture)
{
// Set the current pass as the first texture
// Nastav první texturu
glActiveTextureARB(GL_TEXTURE0_ARB);
}
// Turn on texture mapping and bind the face's texture map
// Zapni texturování a namapuj první texturu podle id textury aktuální plošky
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_uiTextures[p_bsp_face->i_texture_id]);
}
// If we want to render the lightmaps
// Jestliže jsou zapnuty lightmapy
if(g_bLightmaps)
{
// If we want to render the multitexturing
// Jestliže je zapnut multitexturing
if(g_bMultitexture)
{
// Set the current pass as the second lightmap texture
// Nastav druhou texturu
glActiveTextureARB(GL_TEXTURE1_ARB);
}
// Turn on texture mapping and bind the face's lightmap over the texture
// Zapni texturování a namapuj druhou texturu podle id lightmap textury aktuální plošky
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_uiLightmaps[p_bsp_face->i_lightmap_id]);
}
// Zjisti identifikátor textury
unsigned ui_texture_id = m_uiTextures[p_bsp_face->i_texture_id];
// Pokud bude identifikátor textury Quake skyboxu, tak plošku s touto texturou nevykresluj, jinak
if((ui_texture_id != 35) && (ui_texture_id != 36) && (ui_texture_id != 37) &&
(ui_texture_id != 38) && (ui_texture_id != 39))
{
// Draw the face in a triangle face, starting from the starting index
// to the starting index + the number of vertices. This is a vertex array function.
// Vykresli plošky pomocí trojúhelníku podle hodnoty prvního indexu vrcholů a počtu vrcholů
glDrawArrays(GL_TRIANGLE_FAN, p_bsp_face->i_start_vert_index, p_bsp_face->i_num_of_verts);
}
}
|
A to je vše. Děkuji "DigiBen"ovi
za jeho tutorial, ze kterého jsem čerpal zdrojáky a informace. Také jsem použil jeho BSP scénu.
Příště zkusíme rozjet BSP detekce kolizí.
|
|
Home