/*! Model Factory. create the additional scenenodes for ( bullets, health... ) Defines the Entities for Quake3 */ #include #include "q3factory.h" #include "sound.h" using namespace irr; using namespace scene; using namespace gui; using namespace video; using namespace core; using namespace quake3; //! This list is based on the original quake3. static const SItemElement Quake3ItemElement [] = { { "item_health", {"models/powerups/health/medium_cross.md3", "models/powerups/health/medium_sphere.md3"}, "sound/items/n_health.wav", "icons/iconh_yellow", "25 Health", 25, HEALTH, SUB_NONE, SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1 }, { "item_health_large", "models/powerups/health/large_cross.md3", "models/powerups/health/large_sphere.md3", "sound/items/l_health.wav", "icons/iconh_red", "50 Health", 50, HEALTH, SUB_NONE, SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1 }, { "item_health_mega", "models/powerups/health/mega_cross.md3", "models/powerups/health/mega_sphere.md3", "sound/items/m_health.wav", "icons/iconh_mega", "Mega Health", 100, HEALTH, SUB_NONE, SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1 }, { "item_health_small", "models/powerups/health/small_cross.md3", "models/powerups/health/small_sphere.md3", "sound/items/s_health.wav", "icons/iconh_green", "5 Health", 5, HEALTH, SUB_NONE, SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1 }, { "ammo_bullets", "models/powerups/ammo/machinegunam.md3", "", "sound/misc/am_pkup.wav", "icons/icona_machinegun", "Bullets", 50, AMMO, MACHINEGUN, SPECIAL_SFX_BOUNCE, }, { "ammo_cells", "models/powerups/ammo/plasmaam.md3", "", "sound/misc/am_pkup.wav", "icons/icona_plasma", "Cells", 30, AMMO, PLASMAGUN, SPECIAL_SFX_BOUNCE }, { "ammo_rockets", "models/powerups/ammo/rocketam.md3", "", "", "icons/icona_rocket", "Rockets", 5, AMMO, ROCKET_LAUNCHER, SPECIAL_SFX_ROTATE }, { "ammo_shells", "models/powerups/ammo/shotgunam.md3", "", "sound/misc/am_pkup.wav", "icons/icona_shotgun", "Shells", 10, AMMO, SHOTGUN, SPECIAL_SFX_ROTATE }, { "ammo_slugs", "models/powerups/ammo/railgunam.md3", "", "sound/misc/am_pkup.wav", "icons/icona_railgun", "Slugs", 10, AMMO, RAILGUN, SPECIAL_SFX_ROTATE }, { "item_armor_body", "models/powerups/armor/armor_red.md3", "", "sound/misc/ar2_pkup.wav", "icons/iconr_red", "Heavy Armor", 100, ARMOR, SUB_NONE, SPECIAL_SFX_ROTATE }, { "item_armor_combat", "models/powerups/armor/armor_yel.md3", "", "sound/misc/ar2_pkup.wav", "icons/iconr_yellow", "Armor", 50, ARMOR, SUB_NONE, SPECIAL_SFX_ROTATE }, { "item_armor_shard", "models/powerups/armor/shard.md3", "", "sound/misc/ar1_pkup.wav", "icons/iconr_shard", "Armor Shared", 5, ARMOR, SUB_NONE, SPECIAL_SFX_ROTATE }, { "weapon_gauntlet", "models/weapons2/gauntlet/gauntlet.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_gauntlet", "Gauntlet", 0, WEAPON, GAUNTLET, SPECIAL_SFX_ROTATE }, { "weapon_shotgun", "models/weapons2/shotgun/shotgun.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_shotgun", "Shotgun", 10, WEAPON, SHOTGUN, SPECIAL_SFX_ROTATE }, { "weapon_machinegun", "models/weapons2/machinegun/machinegun.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_machinegun", "Machinegun", 40, WEAPON, MACHINEGUN, SPECIAL_SFX_ROTATE }, { "weapon_grenadelauncher", "models/weapons2/grenadel/grenadel.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_grenade", "Grenade Launcher", 10, WEAPON, GRENADE_LAUNCHER, SPECIAL_SFX_ROTATE }, { "weapon_rocketlauncher", "models/weapons2/rocketl/rocketl.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_rocket", "Rocket Launcher", 10, WEAPON, ROCKET_LAUNCHER, SPECIAL_SFX_ROTATE }, { "weapon_lightning", "models/weapons2/lightning/lightning.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_lightning", "Lightning Gun", 100, WEAPON, LIGHTNING, SPECIAL_SFX_ROTATE }, { "weapon_railgun", "models/weapons2/railgun/railgun.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_railgun", "Railgun", 10, WEAPON, RAILGUN, SPECIAL_SFX_ROTATE }, { "weapon_plasmagun", "models/weapons2/plasma/plasma.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_plasma", "Plasma Gun", 50, WEAPON, PLASMAGUN, SPECIAL_SFX_ROTATE }, { "weapon_bfg", "models/weapons2/bfg/bfg.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_bfg", "BFG10K", 20, WEAPON, BFG, SPECIAL_SFX_ROTATE }, { "weapon_grapplinghook", "models/weapons2/grapple/grapple.md3", "", "sound/misc/w_pkup.wav", "icons/iconw_grapple", "Grappling Hook", 0, WEAPON, GRAPPLING_HOOK, SPECIAL_SFX_ROTATE }, { 0 } }; /*! */ const SItemElement * getItemElement ( const stringc& key ) { const SItemElement *item = Quake3ItemElement; while ( item->key ) { if ( 0 == strcmp ( key.c_str(), item->key ) ) return item; item += 1; } return 0; } /*! Quake3 model factory. Takes the mesh buffers and creates scenenodes for their associated shaders */ void Q3ShaderFactory ( Q3LevelLoadParameter &loadParam, IrrlichtDevice *device, IQ3LevelMesh* mesh, eQ3MeshIndex meshIndex, ISceneNode *parent, IMetaTriangleSelector *meta, bool showShaderName ) { if ( 0 == mesh || 0 == device ) return; IMeshSceneNode* node = 0; ISceneManager* smgr = device->getSceneManager(); ITriangleSelector * selector = 0; // the additional mesh can be quite huge and is unoptimized // Save to cast to SMesh SMesh * additional_mesh = (SMesh*) mesh->getMesh ( meshIndex ); if ( 0 == additional_mesh || additional_mesh->getMeshBufferCount() == 0) return; char buf[128]; if ( loadParam.verbose > 0 ) { loadParam.startTime = device->getTimer()->getRealTime(); if ( loadParam.verbose > 1 ) { snprintf_irr(buf, 128, "q3shaderfactory start" ); device->getLogger()->log( buf, ELL_INFORMATION); } } IGUIFont *font = 0; if ( showShaderName ) font = device->getGUIEnvironment()->getFont("fontlucida.png"); IVideoDriver *driver = device->getVideoDriver(); // create helper textures if ( 1 ) { tTexArray tex; u32 pos = 0; getTextures ( tex, "$redimage $blueimage $whiteimage $checkerimage", pos, device->getFileSystem(), driver ); } s32 sceneNodeID = 0; for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount (); ++i ) { IMeshBuffer *meshBuffer = additional_mesh->getMeshBuffer ( i ); const SMaterial &material = meshBuffer->getMaterial(); //! The ShaderIndex is stored in the second material parameter s32 shaderIndex = (s32) material.MaterialTypeParam2; // the meshbuffer can be rendered without additional support, or it has no shader IShader *shader = (IShader *) mesh->getShader ( shaderIndex ); // no shader, or mapped to existing material if ( 0 == shader ) { #if 1 // clone mesh SMesh * m = new SMesh (); m->addMeshBuffer ( meshBuffer ); SMaterial &mat = m->getMeshBuffer( 0 )->getMaterial(); if ( mat.getTexture( 0 ) == 0 ) mat.setTexture ( 0, driver->getTexture ( "$blueimage" ) ); if ( mat.getTexture( 1 ) == 0 ) mat.setTexture ( 1, driver->getTexture ( "$redimage" ) ); IMesh * store = smgr->getMeshManipulator ()->createMeshWith2TCoords ( m ); m->drop(); node = smgr->addMeshSceneNode ( store, parent, sceneNodeID ); node->setAutomaticCulling ( scene::EAC_OFF ); store->drop (); sceneNodeID += 1; #endif } else if ( 1 ) { /* stringc s; dumpShader ( s, shader ); printf ( s.c_str () ); */ // create sceneNode node = smgr->addQuake3SceneNode ( meshBuffer, shader, parent, sceneNodeID ); node->setAutomaticCulling ( scene::EAC_FRUSTUM_BOX ); sceneNodeID += 1; } // show debug shader name if ( showShaderName && node ) { swprintf_irr ( (wchar_t*) buf, 64, L"%hs:%d", node->getName(),node->getID() ); smgr->addBillboardTextSceneNode( font, (wchar_t*) buf, node, dimension2d(80.0f, 8.0f), vector3df(0, 10, 0), sceneNodeID); sceneNodeID += 1; } // create portal rendertargets if ( shader ) { const SVarGroup *group = shader->getGroup(1); if ( group->isDefined( "surfaceparm", "portal" ) ) { } } // add collision // find out if shader is marked as nonsolid u8 doCreate = meta !=0 ; if ( shader ) { const SVarGroup *group = shader->getGroup(1); if ( group->isDefined( "surfaceparm", "trans" ) // || group->isDefined( "surfaceparm", "sky" ) // || group->isDefined( "surfaceparm", "nonsolid" ) ) { if ( !group->isDefined( "surfaceparm", "metalsteps" ) ) { doCreate = 0; } } } if ( doCreate ) { IMesh *m = 0; //! controls if triangles are modified by the scenenode during runtime bool takeOriginal = true; if ( takeOriginal ) { m = new SMesh (); ((SMesh*) m )->addMeshBuffer (meshBuffer); } else { m = node->getMesh(); } //selector = smgr->createOctreeTriangleSelector ( m, 0, 128 ); selector = smgr->createTriangleSelector ( m, 0 ); meta->addTriangleSelector ( selector ); selector->drop (); if ( takeOriginal ) { delete m; } } } #if 0 if ( meta ) { selector = smgr->createOctreeTriangleSelector ( additional_mesh, 0 ); meta->addTriangleSelector ( selector ); selector->drop (); } #endif if ( loadParam.verbose > 0 ) { loadParam.endTime = device->getTimer()->getRealTime (); snprintf_irr(buf, 128, "q3shaderfactory needed %04d ms to create %d shader nodes", loadParam.endTime - loadParam.startTime, sceneNodeID ); device->getLogger()->log(buf, ELL_INFORMATION); } } /*! create items from entity */ void Q3ModelFactory ( Q3LevelLoadParameter &loadParam, IrrlichtDevice *device, IQ3LevelMesh* masterMesh, ISceneNode *parent, bool showShaderName ) { if ( 0 == masterMesh ) return; tQ3EntityList &entity = masterMesh->getEntityList (); ISceneManager* smgr = device->getSceneManager(); char buf[128]; const SVarGroup *group = 0; IEntity search; s32 index; s32 lastIndex; /* stringc s; FILE *f = 0; f = fopen ( "entity.txt", "wb" ); for ( index = 0; (u32) index < entityList.size (); ++index ) { const IEntity *entity = &entityList[ index ]; s = entity->name; dumpShader ( s, entity ); fwrite ( s.c_str(), 1, s.size(), f ); } fclose ( f ); */ IAnimatedMeshMD3* model = 0; SMD3Mesh * mesh = 0; const SMD3MeshBuffer *meshBuffer = 0; IMeshSceneNode* node = 0; ISceneNodeAnimator* anim = 0; const IShader *shader = 0; u32 pos; vector3df p; u32 nodeCount = 0; tTexArray textureArray; IGUIFont *font = 0; if ( showShaderName ) font = device->getGUIEnvironment()->getFont("fontlucida.png"); const SItemElement *itemElement = 0; // walk list for ( index = 0; (u32) index < entity.size(); ++index ) { itemElement = getItemElement ( entity[index].name ); if ( 0 == itemElement ) continue; pos = 0; p = getAsVector3df ( entity[index].getGroup(1)->get ( "origin" ), pos ); nodeCount += 1; for ( u32 g = 0; g < 2; ++g ) { if ( 0 == itemElement->model[g] || itemElement->model[g][0] == 0 ) continue; model = (IAnimatedMeshMD3*) smgr->getMesh( itemElement->model[g] ); if ( 0 == model ) continue; mesh = model->getOriginalMesh(); for ( u32 j = 0; j != mesh->Buffer.size (); ++j ) { meshBuffer = mesh->Buffer[j]; if ( 0 == meshBuffer ) continue; shader = masterMesh->getShader ( meshBuffer->Shader.c_str(), false ); IMeshBuffer *final = model->getMesh(0)->getMeshBuffer(j); if ( shader ) { //!TODO: Hack don't modify the vertexbuffer. make it better;-) final->getMaterial().ColorMask = 0; node = smgr->addQuake3SceneNode ( final, shader, parent ); final->getMaterial().ColorMask = 15; } else { // clone mesh SMesh * m = new SMesh (); m->addMeshBuffer ( final ); node = smgr->addMeshSceneNode ( m, parent ); m->drop(); } if ( 0 == node ) { snprintf_irr ( buf, 128, "q3ModelFactory shader %s failed", meshBuffer->Shader.c_str() ); device->getLogger()->log ( buf ); continue; } // node was maybe centered by shaderscenenode node->setPosition ( p ); node->setName ( meshBuffer->Shader ); node->setAutomaticCulling ( scene::EAC_BOX ); // add special effects to node if ( itemElement->special & SPECIAL_SFX_ROTATE || (g == 0 && itemElement->special & SPECIAL_SFX_ROTATE_1) ) { anim = smgr->createRotationAnimator ( vector3df ( 0.f, 2.f, 0.f ) ); node->addAnimator ( anim ); anim->drop (); } if ( itemElement->special & SPECIAL_SFX_BOUNCE ) { //anim = smgr->createFlyStraightAnimator ( // p, p + vector3df ( 0.f, 60.f, 0.f ), 1000, true, true ); anim = smgr->createFlyCircleAnimator ( p + vector3df( 0.f, 20.f, 0.f ), 20.f, 0.005f, vector3df ( 1.f, 0.f, 0.f ), core::fract ( nodeCount * 0.05f ), 1.f ); node->addAnimator ( anim ); anim->drop (); } } } // show name if ( showShaderName ) { swprintf_irr ( (wchar_t*) buf, sizeof(buf) / 2, L"%hs", itemElement->key ); smgr->addBillboardTextSceneNode( font, (wchar_t*) buf, parent, dimension2d(80.0f, 8.0f), p + vector3df(0, 30, 0), 0); } } // music search.name = "worldspawn"; index = entity.binary_search_multi ( search, lastIndex ); if ( index >= 0 ) { group = entity[ index ].getGroup(1); background_music ( group->get ( "music" ).c_str () ); } // music search.name = "worldspawn"; index = entity.binary_search_multi ( search, lastIndex ); if ( index >= 0 ) { group = entity[ index ].getGroup(1); background_music ( group->get ( "music" ).c_str () ); } //IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2"); //IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh ); } /*! so we need a good starting Position in the level. we can ask the Quake3 Loader for all entities with class_name "info_player_deathmatch" */ s32 Q3StartPosition ( IQ3LevelMesh* mesh, ICameraSceneNode* camera, s32 startposIndex, const vector3df &translation ) { if ( 0 == mesh ) return 0; tQ3EntityList &entityList = mesh->getEntityList (); IEntity search; search.name = "info_player_start"; // "info_player_deathmatch"; // find all entities in the multi-list s32 lastIndex; s32 index = entityList.binary_search_multi ( search, lastIndex ); if ( index < 0 ) { search.name = "info_player_deathmatch"; index = entityList.binary_search_multi ( search, lastIndex ); } if ( index < 0 ) return 0; index += core::clamp ( startposIndex, 0, lastIndex - index ); u32 parsepos; const SVarGroup *group = 0; group = entityList[ index ].getGroup(1); parsepos = 0; vector3df pos = getAsVector3df ( group->get ( "origin" ), parsepos ); pos += translation; parsepos = 0; f32 angle = getAsFloat ( group->get ( "angle"), parsepos ); vector3df target ( 0.f, 0.f, 1.f ); target.rotateXZBy ( angle - 90.f, vector3df () ); if ( camera ) { camera->setPosition ( pos ); camera->setTarget ( pos + target ); //! New. FPSCamera and animators catches reset on animate 0 camera->OnAnimate ( 0 ); } return lastIndex - index + 1; } /*! gets a accumulated force on a given surface */ vector3df getGravity ( const c8 * surface ) { if ( 0 == strcmp ( surface, "earth" ) ) return vector3df ( 0.f, -900.f, 0.f ); if ( 0 == strcmp ( surface, "moon" ) ) return vector3df ( 0.f, -6.f , 0.f ); if ( 0 == strcmp ( surface, "water" ) ) return vector3df ( 0.1f, -2.f, 0.f ); if ( 0 == strcmp ( surface, "ice" ) ) return vector3df ( 0.2f, -9.f, 0.3f ); return vector3df ( 0.f, 0.f, 0.f ); } /* Dynamically load the Irrlicht Library */ #if defined(_IRR_WINDOWS_API_) #ifdef _MSC_VER #pragma comment(lib, "Irrlicht.lib") #endif #include funcptr_createDevice load_createDevice ( const c8 * filename) { return (funcptr_createDevice) GetProcAddress ( LoadLibrary ( filename ), "createDevice" ); } funcptr_createDeviceEx load_createDeviceEx ( const c8 * filename) { return (funcptr_createDeviceEx) GetProcAddress ( LoadLibrary ( filename ), "createDeviceEx" ); } #else // TODO: Dynamic Loading for other os funcptr_createDevice load_createDevice ( const c8 * filename) { return createDevice; } funcptr_createDeviceEx load_createDeviceEx ( const c8 * filename) { return createDeviceEx; } #endif /* get the current collision response camera animator */ ISceneNodeAnimatorCollisionResponse* camCollisionResponse( IrrlichtDevice * device ) { ICameraSceneNode *camera = device->getSceneManager()->getActiveCamera(); ISceneNodeAnimatorCollisionResponse *a = 0; list::ConstIterator it = camera->getAnimators().begin(); for (; it != camera->getAnimators().end(); ++it) { a = (ISceneNodeAnimatorCollisionResponse*) (*it); if ( a->getType() == ESNAT_COLLISION_RESPONSE ) return a; } return 0; } //! internal animation void setTimeFire ( TimeFire *t, u32 delta, u32 flags ) { t->flags = flags; t->next = 0; t->delta = delta; } void checkTimeFire ( TimeFire *t, u32 listSize, u32 now ) { u32 i; for ( i = 0; i < listSize; ++i ) { if ( now < t[i].next ) continue; t[i].next = core::max_ ( now + t[i].delta, t[i].next + t[i].delta ); t[i].flags |= FIRED; } }