19-08-2015, 08:45 PM
Tutorial 16: Quake3 Map Shader Support
Questo tutorial mostra come si carica una mappa di Quake 3, come si crea un nodo della scena per ottimizzare il rendering e come si crea una camera controllata dall'utente.
Partiamo come con l'esempio di HelloWorld: Includiamo i il file header di irrlicht e quello necessario a fare le richieste utente sul driver da usare attraverso la console.
Definiamo quale mappa di Quake3 vogliamo che sia caricata attraverso un po' di costanti.
E ancora, per utilizzare la libreria Irrlicht.DLL, dobbiamo linkare il file theIrrlicht.lib. Potremmo utilizzare le opzioni del progetto, ma è più semplice farlo tramite una pragma comment lib:
Una classe per produrre una serie di screenshots
Ok, iniziamo.
Come nell'esempio HelloWorld, creiamo un device di Irrlicht tramite createDevice(). La differenza è che ora chiederemo all'utente di selezionare quale accellerazione hardware vuole usare. Il device di tipo Software potrebbe essere troppo lento per gestire una mappa molto grossa di Quake 3, ma giusto per puro divertimento decidiamo di rendere possibile questa scelta.
Prendiamoci i puntatori al video driver e quello allo SceneManager in modo da non dovere scrivere sempre device->getVideoDriver() e device->getSceneManager().
Per mostrare la mappa Quake 3 prima dobbiamo caricarla. Le mappe Quake 3 sono registrate dentro a file .pk3, che non sono altro che file .zip. Quindi aggiungiamo il file .pk3 nel nostro FileSystem. Dopo averlo aggiunto saremo in grado di leggere da questo archivio come se fosse una qualsiasi file registrato su disco.
Ora possiamo caricare la mesh della mappa richiamando getMesh(). Riceviamo di ritorno un puntatore ad un IAnimatedMesh. Come sapete le mappe di Quake 3 non sono veramente animate, si tratta di una grossa serie di geometrie statiche con attaccati i rispettivi materiali. In questo caso la IAnimated mesh consiste sono di un frame, quindi ci andremo a prendere la prima e unica “animazione” contenuta, che proprio il nostro livello e ci creiamo sopra una scena Octree, usando la addOctreeSceneNode(). Gli alberi Octree ottimizzano la scena cercando di disegnare solo quelle geometrie che sono visibili. In alternativa al Octree si può sempre usare AnimatedMeshSceneNode, che però disegnerà sempre l'intera geometria del livello, senza alcuna ottimizzazione. Provate: scrivete addAnimatedMeshSceneNode al posto di addOctreeSceneNode e confrontate il disegno delle primitive tramite il video driver. (Esiste il metodo getPrimitiveCountDrawed() della classe IVideoDriver). Notare che l'ottimizzazione con gli Octree ha senso solo se usata su mesh enormi composte da molte geometrie al loro interno.
aggiungiamo la geometria della mesh alla scena (poligoni & patches) la geometria della mesh è ottimizzata per un disegno più veloce ora.
ora costruiamoci un nodo per ciascun shader, l'oggetto è registrato nella mesh del livello come scene::E_Q3_MESH_ITEMS e l'ID dello Shader è registrato nei MaterialParameters, per lo più oscuri, lava in movimento o.. tubi verdi lampeggianti?
Non ci serve altro che una Camera per vedere dentro alla nostra mappa Quake 3. Vogliamo che sia una camera controlalta dall'utente. In Irrlicht ne abbiamo varie disponibili. Per esempio la Maya Camera i il cui controllo è simile a quella presente nel software Maya: Rotazione con la pressione del tasto sinistro del mouse, Zoom con tutti e due i tasti premuti, traslazione con la pressione del tasto detro. La creiamo con addCameraSceneNodeMaya(). Nel nostro esempio però, vogliamo creare una camera di tipo analogo a quello dei giochi first person shooter games (FPS).
adesso ci serve un buon punto da dove partire dentro al livello. Possiamo ottenere dal loader integrato di Irrlicht per le mappe Quake3 tutti i punti chiamati "info_player_deathmatch" tra questi ne scegliamo uno random.
Rendiamo invisibile il cursore del mouse.
Abbiamo fatto tutto, ora disegnamo. Mostriamo anche i frame per secondo e il numero di primitive disegnate sul titolo della finestra. La linea 'if (device->isWindowActive())' è opzionale, ma serve ad evitare il riposizionamento del mouse da parte dell'engine quando si passa da una finestra attiva ad un altra.
Alla fine cancelliamo il device Irrlicht.
Versione pdf scaricabile da QUI
Questo tutorial mostra come si carica una mappa di Quake 3, come si crea un nodo della scena per ottimizzare il rendering e come si crea una camera controllata dall'utente.
Partiamo come con l'esempio di HelloWorld: Includiamo i il file header di irrlicht e quello necessario a fare le richieste utente sul driver da usare attraverso la console.
Codice PHP:
#include <irrlicht.h>
#include "driverChoice.h"
Codice PHP:
#define IRRLICHT_QUAKE3_ARENA
//#define ORIGINAL_QUAKE3_ARENA
//#define CUSTOM_QUAKE3_ARENA
//#define SHOW_SHADER_NAME
#ifdef ORIGINAL_QUAKE3_ARENA
#define QUAKE3_STORAGE_FORMAT addFolderFileArchive
#define QUAKE3_STORAGE_1 "/baseq3/"
#ifdef CUSTOM_QUAKE3_ARENA
#define QUAKE3_STORAGE_2 "/cf/"
#define QUAKE3_MAP_NAME "maps/cf.bsp"
#else
#define QUAKE3_MAP_NAME "maps/q3dm8.bsp"
#endif
#endif
#ifdef IRRLICHT_QUAKE3_ARENA
#define QUAKE3_STORAGE_FORMAT addFileArchive
#define QUAKE3_STORAGE_1 "../../media/map-20kdm2.pk3"
#define QUAKE3_MAP_NAME "maps/20kdm2.bsp"
#endif
using namespace irr;
using namespace scene;
Codice PHP:
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
Codice PHP:
class CScreenShotFactory : public IEventReceiver
{
public:
CScreenShotFactory( IrrlichtDevice *device, const c8 * templateName, ISceneNode* node )
: Device(device), Number(0), FilenameTemplate(templateName), Node(node)
{
FilenameTemplate.replace ( '/', '_' );
FilenameTemplate.replace ( '\\', '_' );
}
bool OnEvent(const SEvent& event)
{
// check if user presses the key F9
if ((event.EventType == EET_KEY_INPUT_EVENT) &&
event.KeyInput.PressedDown)
{
if (event.KeyInput.Key == KEY_F9)
{
video::IImage* image = Device->getVideoDriver()->createScreenShot();
if (image)
{
c8 buf[256];
snprintf(buf, 256, "%s_shot%04d.jpg",
FilenameTemplate.c_str(),
++Number);
Device->getVideoDriver()->writeImageToFile(image, buf, 85 );
image->drop();
}
}
else
if (event.KeyInput.Key == KEY_F8)
{
if (Node->isDebugDataVisible())
Node->setDebugDataVisible(scene::EDS_OFF);
else
Node->setDebugDataVisible(scene::EDS_BBOX_ALL);
}
}
return false;
}
private:
IrrlichtDevice *Device;
u32 Number;
core::stringc FilenameTemplate;
ISceneNode* Node;
};
Codice PHP:
int IRRCALLCONV main(int argc, char* argv[])
{
Codice PHP:
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device and exit if creation failed
const core::dimension2du videoDim(800,600);
IrrlichtDevice *device = createDevice(driverType, videoDim, 32, false );
if (device == 0)
return 1; // could not create selected driver.
const char* mapname=0;
if (argc>2)
mapname = argv[2];
else
mapname = QUAKE3_MAP_NAME;
Codice PHP:
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
device->getFileSystem()->addFileArchive("../../media/");
Codice PHP:
if (argc>2)
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(argv[1]);
else
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(QUAKE3_STORAGE_1);
#ifdef QUAKE3_STORAGE_2
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(QUAKE3_STORAGE_2);
#endif
// Quake3 Shader controls Z-Writing
smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
Codice PHP:
scene::IQ3LevelMesh* const mesh =
(scene::IQ3LevelMesh*) smgr->getMesh(mapname);
Codice PHP:
scene::ISceneNode* node = 0;
if (mesh)
{
scene::IMesh * const geometry = mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY);
node = smgr->addOctreeSceneNode(geometry, 0, -1, 4096);
}
// create an event receiver for making screenshots
CScreenShotFactory screenshotFactory(device, mapname, node);
device->setEventReceiver(&screenshotFactory);
Codice PHP:
if ( mesh )
{
// the additional mesh can be quite huge and is unoptimized
const scene::IMesh * const additional_mesh = mesh->getMesh(quake3::E_Q3_MESH_ITEMS);
#ifdef SHOW_SHADER_NAME
gui::IGUIFont *font = device->getGUIEnvironment()->getFont("../../media/fontlucida.png");
u32 count = 0;
#endif
for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount(); ++i )
{
const IMeshBuffer* meshBuffer = additional_mesh->getMeshBuffer(i);
const video::SMaterial& material = meshBuffer->getMaterial();
// The ShaderIndex is stored in the material parameter
const s32 shaderIndex = (s32) material.MaterialTypeParam2;
// the meshbuffer can be rendered without additional support, or it has no shader
const quake3::IShader *shader = mesh->getShader(shaderIndex);
if (0 == shader)
{
continue;
}
// we can dump the shader to the console in its
// original but already parsed layout in a pretty
// printers way.. commented out, because the console
// would be full...
// quake3::dumpShader ( Shader );
node = smgr->addQuake3SceneNode(meshBuffer, shader);
#ifdef SHOW_SHADER_NAME
count += 1;
core::stringw name( node->getName() );
node = smgr->addBillboardTextSceneNode(
font, name.c_str(), node,
core::dimension2d<f32>(80.0f, 8.0f),
core::vector3df(0, 10, 0));
#endif
}
}
Codice PHP:
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
Codice PHP:
if ( mesh )
{
quake3::tQ3EntityList &entityList = mesh->getEntityList();
quake3::IEntity search;
search.name = "info_player_deathmatch";
s32 index = entityList.binary_search(search);
if (index >= 0)
{
s32 notEndList;
do
{
const quake3::SVarGroup *group = entityList[index].getGroup(1);
u32 parsepos = 0;
const core::vector3df pos =
quake3::getAsVector3df(group->get("origin"), parsepos);
parsepos = 0;
const f32 angle = quake3::getAsFloat(group->get("angle"), parsepos);
core::vector3df target(0.f, 0.f, 1.f);
target.rotateXZBy(angle);
camera->setPosition(pos);
camera->setTarget(pos + target);
++index;
notEndList = ( index < (s32) entityList.size () && entityList[index].name == search.name && (device->getTimer()->getRealTime() >> 3 ) & 1 );
notEndList = index == 2;
} while ( notEndList );
}
}
Codice PHP:
device->getCursorControl()->setVisible(false);
// load the engine logo
gui->addImage(driver->getTexture("irrlichtlogo2.png"),
core::position2d<s32>(10, 10));
// show the driver logo
const core::position2di pos(videoDim.Width - 128, videoDim.Height - 64);
switch ( driverType )
{
case video::EDT_BURNINGSVIDEO:
gui->addImage(driver->getTexture("burninglogo.png"), pos);
break;
case video::EDT_OPENGL:
gui->addImage(driver->getTexture("opengllogo.png"), pos);
break;
case video::EDT_DIRECT3D8:
case video::EDT_DIRECT3D9:
gui->addImage(driver->getTexture("directxlogo.png"), pos);
break;
}
Codice PHP:
int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255,20,20,40));
smgr->drawAll();
gui->drawAll();
driver->endScene();
int fps = driver->getFPS();
//if (lastFPS != fps)
{
io::IAttributes * const attr = smgr->getParameters();
core::stringw str = L"Q3 [";
str += driver->getName();
str += "] FPS:";
str += fps;
#ifdef _IRR_SCENEMANAGER_DEBUG
str += " Cull:";
str += attr->getAttributeAsInt("calls");
str += "/";
str += attr->getAttributeAsInt("culled");
str += " Draw: ";
str += attr->getAttributeAsInt("drawn_solid");
str += "/";
str += attr->getAttributeAsInt("drawn_transparent");
str += "/";
str += attr->getAttributeAsInt("drawn_transparent_effect");
#endif
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
Codice PHP:
device->drop();
return 0;
}
Versione pdf scaricabile da QUI