#include #include #include <../CEGUIRenderer/include/OgreCEGUIRenderer.h> #include <../CEGUIRenderer/include/OgreCEGUIResourceProvider.h> #include <../CEGUIRenderer/include/OgreCEGUITexture.h> #include #include #include #include "TestCullingTerrainApplication.h" #include "TerrainFrameListener.h" #if COLLADA #include "OgreColladaPrerequisites.h" #include "OgreColladaDocument.h" #include "OgreColladaScene.h" #endif #include "IVReader.h" #define WIN32_LEAN_AND_MEAN #include const static bool USE_STATIC_GEOMETRY = false; bool TestCullingTerrainApplication::msShowHillyTerrain = false; /*************************************************************/ /* EntityState implementation */ /*************************************************************/ Vector3 EntityState::msMinPos = Vector3::ZERO; Vector3 EntityState::msMaxPos = Vector3::ZERO; EntityState::EntityState(Entity *ent, State entityState, Real speed): mEntity(ent), mState(entityState), mAnimationSpeed(speed) { switch(entityState) { case MOVING: mAnimationState = mEntity->getAnimationState("Walk"); break; case WAITING: mAnimationState = mEntity->getAnimationState("Idle"); break; case STOP: mAnimationState = NULL; break; default: break; } // enable animation state if (mAnimationState) { mAnimationState->setLoop(true); mAnimationState->setEnabled(true); } mTimeElapsed = Math::RangeRandom(1, 5); } //----------------------------------------------------------------------- EntityState::~EntityState() { mAnimationState = NULL; mEntity = NULL; } //----------------------------------------------------------------------- Entity *EntityState::GetEntity() { return mEntity; } //----------------------------------------------------------------------- EntityState::State EntityState::GetState() { return mState; } //----------------------------------------------------------------------- void EntityState::update(Real timeSinceLastFrame) { mTimeElapsed -= timeSinceLastFrame * mAnimationSpeed; if (!mEntity || !mAnimationState) return; if (mState == MOVING) // toggle between moving (longer) and waiting (short) { SceneNode *parent = mEntity->getParentSceneNode(); if (!parent) return; if (mTimeElapsed <= 0) // toggle animation state { if (mAnimationState->getAnimationName() == "Idle") { SetAnimationState("Walk", true); mTimeElapsed = walk_duration; // walk for mTimeElapsed units // choose random rotation Radian rnd = Radian(Math::UnitRandom() * Math::PI * rotate_factor); //mEntity->getParentSceneNode()->rotate(); parent->yaw(rnd); } else { SetAnimationState("Idle", true); mTimeElapsed = wait_duration; // wait for mTimeElapsed seconds } } if (mAnimationState->getAnimationName() == "Walk") // move forward { // store old position, just in case we get out of bounds Vector3 oldPos = parent->getPosition(); parent->translate(parent->getLocalAxes(), Vector3(move_factor * mAnimationSpeed, 0, 0)); // HACK: if out of bounds => reset to old position and set animationstate to idle if (OutOfBounds(parent)) { parent->setPosition(oldPos); SetAnimationState("Idle", true); mTimeElapsed = wait_duration; } } } // add time to drive animation mAnimationState->addTime(timeSinceLastFrame * mAnimationSpeed); } //----------------------------------------------------------------------- void EntityState::SetAnimationState(String stateStr, bool loop) { if (!mEntity) return; mAnimationState = mEntity->getAnimationState(stateStr); mAnimationState->setLoop(loop); mAnimationState->setEnabled(true); } //----------------------------------------------------------------------- bool EntityState::OutOfBounds(SceneNode *node) { Vector3 pos = node->getPosition(); if ((pos > msMinPos) && (pos < msMaxPos)) return false; return true; } /********************************************************************/ /* TestCullingTerrainApplication implementation */ /********************************************************************/ TestCullingTerrainApplication::TestCullingTerrainApplication(): mTerrainContentGenerator(NULL), mRayQueryExecutor(NULL), mIVReader(NULL), mFilename("terrain"), mEnvironmentFilename("generate_viewcells.env") { } //------------------------------------------------------------------------- void TestCullingTerrainApplication::loadConfig(const String& filename) { // TODO matt // Set up the options ConfigFile config; String val; config.load(filename); std::stringstream d; d << "reading the config file from: " << filename; LogManager::getSingleton().logMessage(d.str()); val = config.getSetting("Scene"); if (!val.empty()) { mFilename = val.c_str(); d << "\nloading scene from file: " << mFilename; LogManager::getSingleton().logMessage(d.str()); } /*val = config.getSetting("ViewCells"); if (!val.empty()) { mViewCellsFilename = val.c_str();}*/ val = config.getSetting("VisibilityEnvironment"); if (!val.empty()) { mEnvironmentFilename = val.c_str(); std::stringstream d; d << "loading environment from file: " << mEnvironmentFilename; LogManager::getSingleton().logMessage(d.str()); } Vector3 v = Vector3::UNIT_SCALE; val = config.getSetting("ViewX"); if (!val.empty()) v.x = atof( val.c_str() ); val = config.getSetting("ViewY"); if (!val.empty()) v.y = atof(val.c_str()); val = config.getSetting("ViewZ"); if (!val.empty()) v.z = atof( val.c_str()); ///////////////////////// // setup scene //-- load the scene from specified file if (!LoadScene(mFilename)) LogManager::getSingleton().logMessage("error loading scene"); GtpVisibility::VisibilityManager *mVisManager = NULL; //-- load the environment from file if (mSceneMgr->getOption("VisibilityManager", &mVisManager)) { GtpVisibility::VisibilityEnvironment *visEnv = mVisManager->GetVisibilityEnvironment(); if (!visEnv->LoadEnvironment(mEnvironmentFilename)) { std::stringstream d; d << "failed loading environment from " << mEnvironmentFilename; LogManager::getSingleton().logMessage(d.str()); } else //-- load environment options { //-- get view cells file name, load view cells only on demand mViewCellsFilename = visEnv->getViewCellsFileName(); std::stringstream d; d << "view cells file name: " << mViewCellsFilename; LogManager::getSingleton().logMessage(d.str()); } } else { LogManager::getSingleton().logMessage("error: no visibility scenemanager available"); } // set camera position accordingly mCamNode->setPosition(v); } //----------------------------------------------------------------------- TestCullingTerrainApplication::~TestCullingTerrainApplication() { OGRE_DELETE(mTerrainContentGenerator); OGRE_DELETE(mRayQueryExecutor); OGRE_DELETE(mIVReader); deleteEntityStates(); } //----------------------------------------------------------------------- void TestCullingTerrainApplication::deleteEntityStates() { for (int i = 0; i < (int)mEntityStates.size(); ++i) { OGRE_DELETE(mEntityStates[i]); } mEntityStates.clear(); } //----------------------------------------------------------------------- void TestCullingTerrainApplication::createCamera() { // create the camera mCamera = mSceneMgr->createCamera("PlayerCam"); /** set a nice viewpoint * we use a camera node here and apply all transformations on it instead * of applying all transformations directly to the camera * because then the camera is displayed correctly in the visualization */ mCamNode = mSceneMgr->getRootSceneNode()-> createChildSceneNode("CamNode1", Vector3(707, 5000, 528)); mCamNode->setOrientation(Quaternion(-0.3486, 0.0122, 0.9365, 0.0329)); mCamNode->attachObject(mCamera); //-- create visualization camera mVizCamera = mSceneMgr->createCamera("VizCam"); mVizCamera->setPosition(mCamNode->getPosition()); mVizCamera->setNearClipDistance(1); mCamera->setNearClipDistance(1); // infinite far plane? if (mRoot->getRenderSystem()->getCapabilities()->hasCapability(RSC_INFINITE_FAR_PLANE)) { mVizCamera->setFarClipDistance(0); mCamera->setFarClipDistance(0); } else { mVizCamera->setFarClipDistance(20000); mCamera->setFarClipDistance(20000); } } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::setup() { bool carryOn = ExampleApplication::setup(); if (carryOn) createRenderTargetListener(); return carryOn; } //----------------------------------------------------------------------- void TestCullingTerrainApplication::createRenderTargetListener() { mWindow->addListener(new VisualizationRenderTargetListener(mSceneMgr)); } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::LoadSceneCollada(const String &filename, SceneNode *root, const int index) { #if COLLADA // Collada ColladaDocument *daeDoc = new ColladaDocument(mSceneMgr); String daeName = filename; //if (daeName.empty()) // daeName = "City_1500.dae"; // default directory //daeName.insert(0, "../../media/models/collada/City"); LogManager::getSingleton().logMessage("ColladaDocument - import started"); // full import if (daeDoc->doImport(daeName)) { LogManager::getSingleton().logMessage("yuppi"); /** * build up scene * if you only want a specific part of the scene graph, you must fetch a scene node * by its unique node name and give it as parameter * e.g. * ColladaSceneNode *box = mScene->getNode("Box2"); * if (box != NULL) box->createOgreInstance(NULL); */ daeDoc->getScene()->createOgreInstance(NULL); // everything is loaded, we do not need the collada document anymore delete daeDoc; return true; } else { LogManager::getSingleton().logMessage("ColladaDocument - import failed"); return false; } #endif return true; } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::LoadSceneIV(const String &filename, SceneNode *root, const int index) { mIVReader = new IVReader(); Timer *timer = PlatformManager::getSingleton().createTimer(); timer->reset(); if (1) { std::string logFilename = "IVLog" + Ogre::StringConverter().toString(index) + ".log"; Log *log = LogManager::getSingleton().createLog(logFilename); mIVReader->setLog(log); } //viennaNode->translate(Vector3(-300, -300, 0)); if (mIVReader->loadFile(filename.c_str())) { SceneNode *node = root->createChildSceneNode("IVSceneNode" + index); mIVReader->buildTree(mSceneMgr, node); mIVReader->collapse(); OGRE_DELETE(mIVReader); std::stringstream d; d << "loaded " << filename << " in " << timer->getMilliseconds() * 1e-3 << " secs"; LogManager::getSingleton().logMessage(d.str()); PlatformManager::getSingleton().destroyTimer(timer); //-- bake into static geometry if (USE_STATIC_GEOMETRY) { BakeSceneIntoStaticGeometry("staticVienna", "Vienna"); } return true; } return false; } //-------------------------------------------------------- void TestCullingTerrainApplication::BakeSceneIntoStaticGeometry(const String &staticGeomName, const String &nodeName) { #if OGRE_103 // note: different static geom for roofs, roads, ..., becazse they have same material StaticGeometry *staticGeom = mSceneMgr->createStaticGeometry(staticGeomName); // note: looping over entities here. why does scene node not work? SceneManager::EntityIterator it = mSceneMgr->getEntityIterator(); while (it.hasMoreElements()) { Entity *ent = it.getNext(); ent->setVisible(false); staticGeom->addEntity(ent, ent->getParentSceneNode()->getPosition()); } staticGeom->setRegionDimensions(Vector3(100,100,100)); staticGeom->build(); // cleanup node //wallsNode->detachAllObjects(); //roofsNode->detachAllObjects(); //roadsNode->detachAllObjects(); //planeNode->detachAllObjects(); //viennaNode->removeChild("Walls"); mSceneMgr->destroySceneNode(nodeName); #endif } //-------------------------------------------------------- void TestCullingTerrainApplication::createScene() { //-- load scene loadConfig("terrainCulling.cfg"); ///////////////////////////////////// // Set ambient light mAmbientLight = ColourValue(0.5, 0.5, 0.5); mSceneMgr->setAmbientLight(mAmbientLight); //-- create light mSunLight = mSceneMgr->createLight("SunLight"); mSunLight->setType(Light::LT_DIRECTIONAL); //mSunLight->setType(Light::LT_SPOTLIGHT); //mSunLight->setSpotlightRange(Degree(30), Degree(50)); mSunLight->setPosition(707, 2000, 500); mSunLight->setCastShadows(true); // set light angle not too small over the surface, otherwise shadows textures will be broken Vector3 dir(0.5, 1, 0.5); dir.normalise(); mSunLight->setDirection(dir); //mSunLight->setDirection(Vector3::NEGATIVE_UNIT_Y); mSunLight->setDiffuseColour(1, 1, 1); mSunLight->setSpecularColour(1, 1, 1); // -- Fog // NB it's VERY important to set this before calling setWorldGeometry // because the vertex program picked will be different ColourValue fadeColour(0.93, 0.86, 0.76); mWindow->getViewport(0)->setBackgroundColour(fadeColour); //mSceneMgr->setFog( FOG_LINEAR, fadeColour, .001, 500, 1000); // Create a skybox mSceneMgr->setSkyBox(true, "Examples/CloudyNoonSkyBox", 5000, true); if (msShowHillyTerrain) { std::string terrain_cfg("terrain.cfg"); #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE terrain_cfg = mResourcePath + terrain_cfg; #endif mSceneMgr->setWorldGeometry(terrain_cfg); } //-- CEGUI setup setupGui(); // occluder plane to test visibility if (0) { Plane plane; plane.normal = Vector3::UNIT_Y; plane.d = -60; MeshManager::getSingleton().createPlane("Myplane", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 5000,5000,100,100,true,1,5,5,Vector3::UNIT_Z); Entity* pPlaneEnt = mSceneMgr->createEntity( "plane", "Myplane" ); pPlaneEnt->setMaterialName("Examples/Rockwall"); pPlaneEnt->setCastShadows(true); mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(pPlaneEnt); } // Warning: In GL since we can't go higher than the window res mSceneMgr->setShadowTextureSettings(1024, 2); mSceneMgr->setShadowColour(ColourValue(0.5, 0.5, 0.5)); //-- terrain content setup // HACK: necessary to call once before the content creation for // terrain initialisation mSceneMgr->_renderScene(mCamera, mWindow->getViewport(0), true); // ray query executor: needed to clamp to terrain mRayQueryExecutor = new RayQueryExecutor(mSceneMgr); mTerrainMinPos = EntityState::msMinPos = Vector3(0, 0, 0); mTerrainMaxPos = EntityState::msMaxPos = Vector3(5000, 5000, 5000); mTerrainContentGenerator = new TerrainContentGenerator(mSceneMgr); if (!msShowHillyTerrain) return; // if no objects in file, we generate new objects if (!mTerrainContentGenerator->LoadObjects("objects.out")) { // the objects are generated randomly distributed over the terrain if (1) generateScene(1500, 0); // create robots if (0) generateScene(100, 1); // create trees if (0) generateScene(100, 2); // create ninjas } } //----------------------------------------------------------------------- void TestCullingTerrainApplication::generateScene(int num, int objectType) { float val = TerrainFrameListener::msObjectScales[objectType]; Vector3 scale(val, val, val); const float maxHeight = 75; // In order to provide much occlusion, // height is restricted to maxHeight => no objects are created on peaks mTerrainContentGenerator->SetMinPos(Vector3(mTerrainMinPos)); mTerrainContentGenerator->SetMaxPos(Vector3(mTerrainMaxPos.x, maxHeight, mTerrainMaxPos.z)); //std::stringstream d; d << "objscale: " << scale[0]; //Ogre::LogManager::getSingleton().logMessage(d.str()); mTerrainContentGenerator->SetScale(scale); mTerrainContentGenerator->SetOffset(TerrainFrameListener::msObjectTerrainOffsets[objectType]); mTerrainContentGenerator->GenerateScene(num, TerrainFrameListener::msObjectCaptions[objectType]); if (objectType != 0) // from our objects, only robot has animation phases return; EntityList *entList = mTerrainContentGenerator->GetGeneratedEntities(); //-- add animation state for new robots (located at the end of the list) for (int i = (int)entList->size() - num; i < (int)entList->size(); ++i) { mEntityStates.push_back(new EntityState((*entList)[i], EntityState::WAITING, Math::RangeRandom(0.5, 1.5))); } // release limitations on height => it is possible for the user to put single // objects on peaks of the terrain (will be only few, not relevant for occlusion) mTerrainContentGenerator->SetMaxPos(mTerrainMaxPos); } //----------------------------------------------------------------------- void TestCullingTerrainApplication::updateAnimations(Real timeSinceLastFrame) { for (int i = 0; i < (int)mEntityStates.size(); ++i) { SceneNode *sn = mEntityStates[i]->GetEntity()->getParentSceneNode(); mEntityStates[i]->update(timeSinceLastFrame); if (mEntityStates[i]->GetState() == EntityState::MOVING) { Clamp2Terrain(sn, 0); //sn->setNodeVisible(false); } } } //----------------------------------------------------------------------- EntityStates &TestCullingTerrainApplication::getEntityStates() { return mEntityStates; } //----------------------------------------------------------------------- void TestCullingTerrainApplication::setupGui() { #if OGRE103 mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, ST_EXTERIOR_CLOSE); #else mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr); #endif mGUISystem = new CEGUI::System(mGUIRenderer); // Mouse CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLook.scheme"); CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow"); mGUISystem->setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow"); CEGUI::MouseCursor::getSingleton().hide(); } //----------------------------------------------------------------------- void TestCullingTerrainApplication::createFrameListener() { mTerrainFrameListener = new TerrainFrameListener(mWindow, mCamera, mSceneMgr, mGUIRenderer, mTerrainContentGenerator, mVizCamera, mCamNode, mSunLight, this); mRoot->addFrameListener(mTerrainFrameListener); } //----------------------------------------------------------------------- void TestCullingTerrainApplication::chooseSceneManager() { #ifdef OGRE_103 if (msShowHillyTerrain) { // Terrain scene manager mSceneMgr = mRoot->getSceneManager(ST_EXTERIOR_CLOSE); } else { // octree scene manager mSceneMgr = mRoot->getSceneManager(ST_GENERIC); } #else if (msShowHillyTerrain) { //mSceneMgr = mRoot->createSceneManager("TerrainSceneManager"); mSceneMgr = mRoot->createSceneManager("OcclusionCullingSceneManager"); //mSceneMgr = mRoot->createSceneManager("KdTreeSceneManager"); } else { //mSceneMgr = mRoot->createSceneManager("OctreeSceneManager"); mSceneMgr = mRoot->createSceneManager("OcclusionCullingSceneManager"); //mSceneMgr = mRoot->createSceneManager("KdTreeSceneManager"); } #endif } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::Clamp2Terrain(SceneNode *node, int terrainOffs) { // clamp scene node to terrain Vector3 pos = node->getPosition(); Vector3 queryResult; if (mRayQueryExecutor->executeRayQuery(&queryResult, Vector3(pos.x, MAX_HEIGHT, pos.z), Vector3::NEGATIVE_UNIT_Y)) { node->setPosition(pos.x, queryResult.y + terrainOffs, pos.z); return true; } return false; } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::Clamp2FloorPlane() { // clamp to floor plane RaySceneQuery *raySceneQuery = mSceneMgr->createRayQuery( Ray(mCamNode->getPosition(), Vector3::NEGATIVE_UNIT_Y)); RaySceneQueryResult& qryResult = raySceneQuery->execute(); RaySceneQueryResult::iterator rit = qryResult.begin(); while (rit != qryResult.end() && rit->movable) { if (rit->movable->getName() != "PlayerCam") { mCamNode->setPosition(mCamNode->getPosition().x, rit->movable->getWorldBoundingBox().getCenter().y + 2, mCamNode->getPosition().z); /* std::stringstream d; d << "World: " << it->movable->getWorldBoundingBox().getCenter().y << ", Object: " << it->movable->getBoundingBox().getCenter().y << ", Camera: " << mCamera->getDerivedPosition(); LogManager::getSingleton().logMessage(d.str());*/ return true; } ++ rit; } OGRE_DELETE(raySceneQuery); return false; } //----------------------------------------------------------------------- // splits strings containing multiple file names static int SplitFilenames(const std::string str, std::vector &filenames) { int pos = 0; while(1) { int npos = (int)str.find(';', pos); if (npos < 0 || npos - pos < 1) break; filenames.push_back(std::string(str, pos, npos - pos)); pos = npos + 1; } filenames.push_back(std::string(str, pos, str.size() - pos)); return (int)filenames.size(); } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::LoadScene(const String &filename) { using namespace std; // use leaf nodes of the original spatial hierarchy as occludees vector filenames; int files = SplitFilenames(filename, filenames); std::stringstream d; d << "number of input files: " << files << "\n"; LogManager::getSingleton().logMessage(d.str()); bool result = false; vector::const_iterator fit, fit_end = filenames.end(); int i = 0; // root for different files for (fit = filenames.begin(); fit != fit_end; ++ fit, ++ i) { const string fn = *fit; if (strstr(fn.c_str(), ".iv") || strstr(fn.c_str(), ".wrl")) { // hack: set postion manually for vienna //mCamNode->setPosition(Vector3(830, 300, -540)); //mCamNode->setOrientation(Quaternion(-0.3486, 0.0122, 0.9365, 0.0329)); // load iv files if (!LoadSceneIV(fn, mSceneMgr->getRootSceneNode(), i)) { // terrain hack msShowHillyTerrain = true; LogManager::getSingleton().logMessage("error loading scene. loading terrain instead"); } } else if (strstr(filename.c_str(), ".dae")) { // load collada files // load iv files LoadSceneCollada(fn, mSceneMgr->getRootSceneNode(), i); } else //if (filename == "terrain") { // terrain hack msShowHillyTerrain = true; LogManager::getSingleton().logMessage("loading terrain"); } result = true; } /* if (result) { int intersectables, faces; std::stringstream d; d << filename << " parsed successfully.\n" << "#NUM_OBJECTS (Total numner of objects)\n" << intersectables << "\n" << "#NUM_FACES (Total numner of faces)\n" << faces << "\n"; } */ return result; } //----------------------------------------------------------------------- bool TestCullingTerrainApplication::LoadViewCells(const String &filename) { LogManager::getSingleton().logMessage("loading view cells"); //-- the actual loading happens here return mSceneMgr->setOption("LoadViewCells", filename.c_str()); } /**********************************************************************/ /* VisualizationRenderTargetListener implementation */ /**********************************************************************/ //----------------------------------------------------------------------- VisualizationRenderTargetListener::VisualizationRenderTargetListener(SceneManager *sceneMgr) :RenderTargetListener(), mSceneMgr(sceneMgr) { } //----------------------------------------------------------------------- void VisualizationRenderTargetListener::preViewportUpdate(const RenderTargetViewportEvent &evt) { // visualization viewport const bool showViz = evt.source->getZOrder() == VIZ_VIEWPORT_Z_ORDER; const bool nShowViz = !showViz; mSavedShadowTechnique = mSceneMgr->getShadowTechnique(); mSavedAmbientLight = mSceneMgr->getAmbientLight(); // -- ambient light must be full for visualization, shadows disabled if (showViz) { mSceneMgr->setAmbientLight(ColourValue(1, 1, 1)); mSceneMgr->setShadowTechnique(SHADOWTYPE_NONE); } mSceneMgr->setOption("PrepareVisualization", &showViz); mSceneMgr->setOption("SkyBoxEnabled", &nShowViz); //mSceneMgr->setOption("SkyPlaneEnabled", &showViz); RenderTargetListener::preViewportUpdate(evt); } //----------------------------------------------------------------------- void VisualizationRenderTargetListener::postRenderTargetUpdate(const RenderTargetEvent &evt) { // reset values mSceneMgr->setShadowTechnique(mSavedShadowTechnique); mSceneMgr->setAmbientLight(mSavedAmbientLight); RenderTargetListener::postRenderTargetUpdate(evt); } //----------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) { // Create application object TestCullingTerrainApplication app; try { app.go(); } catch( Ogre::Exception& e ) { MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL); } return 0; }