Table of contents
Introduction
This tutorial will guide you through the process of creating a Paging Landscape Scene Manager application from scratch. It is assumed that you have already downloaded and built the Paging Scene Manager plugin as described in this tutorial.
Requirements
You will need the following applications to complete this tutorial successfully.
- PLSM Map Editor - Used to create heightmaps for the Paging Landscape Scene Manager
- Map Splitter - Used to split a heightmap into multiple pages
Both need to be in a directory with a resources.cfg that have a dedicated section containing references to resources used by the tools (maps, texture, material, etc...). Unless You change it, by default its '[PLSM2]'
Creating the Map
We will now cover the process needed to create a simple heightmap with the PLSM Map Editor, splitting it into pages with the Map Splitter and moving the split files into the appropriate media directories.
Map editor Will soon generate and split map using GUI.
Otherwise for fine tuning or batch mode terrains splitting, create a *.gen.cfg file, that you reference in the maptools.cfg that is in the same directory as mapstplitter binary.
Configuring the Project
This section will cover what special configurations you have to make (such as modifying Visual Studio Project settings, modifying configuration files or moving of PLSM DLL files) in order to link the Paging Landscape Scene Manager to your application.
If you use the plsm2 Listener system, you need to link directly to the library.
In both case you need to load it at run-time using the plugins.cfg plugin load listing.
Creating the Application
In this section, you will be guided through the process of configuring your application to use the Paging Landscape Scene Manager as your default scene manager and loading the map you created with the PLSM Map Editor
//Here's how to select the plugin SceneManager *SceneMgr = Root::getSingleton().getSceneManager( ST_EXTERIOR_REAL_FAR ); //Here's how to load the plugin configuration file // you can use the name you want. (it must be the config file inside...) SceneMgr ->setWorldGeometry( "paginglandscape2.cfg" );
A very simple Application
This application was written by a Newbie on a Linux (Kubuntu) box using Kdevelop. You may have to tweak it a bit to get it to run on your system.
It is a very basic app., all it does is display a terrain and allow camera movement. It is independant of both the Ogre & Paginglandscape samples and so could form the basis for further development and trials (This is what I'm using it for )
I am assuming that you have worked through the Ogre basic tutorials and have run through the paginglandscape sample demo including MapSplitter.
1) Prepare your IDE for Ogre as described in Setting Up An Application - Shoggoth and create a project adding a source file which contains this code:-
#include "Ogre.h" #include "OgreErrorDialog.h" #include "FrameListener.h" #include "OgreConfigFile.h" #include "OgreStringConverter.h" #if defined(WIN32) # include <windows.h> #endif using namespace Ogre; class Application { public: //Constructor Application() { mFrameListener = 0; mRoot = 0; } //Destructor ~Application() { if(mFrameListener) delete mFrameListener; if(mRoot) delete mRoot; } void go(void) { if(!setup()) return; mRoot->startRendering(); //Clean up destroyScene(); } private: Root* mRoot; MyFrameListener* mFrameListener; RenderWindow* mWindow; SceneManager* mSceneMgr; Camera* mCamera; bool setup(void) { mRoot = new Root(); setupResources(); bool carryOn = configure(); if(!carryOn) return false; chooseSceneManager(); createCamera(); createViewports(); ///set default mipmap level (NB some API's ignore this) TextureManager::getSingleton().setDefaultNumMipmaps(5); /// Create resource listeners ( for loading screens) createResourceListener(); loadResources(); createScene(); createFrameListener(); return true; } void destroyScene() { } void setupResources(void) { ConfigFile cf; cf.load("resources.cfg"); ///go thro' all sections & settings in the file ConfigFile::SectionIterator seci = cf.getSectionIterator(); String secName, typeName, archName; while(seci.hasMoreElements()) { secName = seci.peekNextKey(); ConfigFile::SettingsMultiMap *settings = seci.getNext(); ConfigFile::SettingsMultiMap::iterator i; for(i = settings->begin(); i != settings->end(); ++i) { typeName = i->first; archName = i->second; ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName); } ///endfor } ///endwhile } ///Configures the application returns false if the user abandons. bool configure(void) { if(mRoot->showConfigDialog()) { mWindow = mRoot->initialise(true); return true; } else { return false; } ///endif } void chooseSceneManager(void) { mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE); } void createCamera(void) { mCamera = mSceneMgr->createCamera("PlayerCam"); //mCamera->setPosition(Vector3(0,40,500)); //mCamera->lookAt(Vector3(160,0,-10)); mCamera->setNearClipDistance(5); } void createViewports(void) { /// Create one viewport for whole window Viewport* vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(ColourValue::Blue); /// Alter the camera aspect ratio to match viewport mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight())); } void createResourceListener(void) { } void loadResources(void) { /// This is the minimum requirement. Indivual resources can be loaded ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); } void createScene(void) { mSceneMgr->setWorldGeometry("terrain.cfg"); /* mSceneMgr->setAmbientLight(ColourValue(0.5,0.5,0.5)); Entity* ent1 = mSceneMgr->createEntity("Robot","robot.mesh"); SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "RobotNode" ); node->attachObject( ent1); node->setPosition( Vector3(10,220,-400) ); Light* light1 = mSceneMgr->createLight( "Light1"); light1->setType( Light::LT_POINT); light1->setPosition( Vector3(250, 150, 250) ); light1->setDiffuseColour( ColourValue::White ); light1->setSpecularColour( ColourValue::White ); */ // Create the scene node SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "CamNode1", Vector3( -400, 200, 400 ) ); // Make it look towards the ninja node->yaw( Degree(-45) ); // Create the pitch node node = node->createChildSceneNode( "PitchNode1" ); node->attachObject( mCamera ); // create the second camera node/pitch node //node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "CamNode2", Vector3( 0, 200, 400 ) ); //node = node->createChildSceneNode( "PitchNode2" ); } void createFrameListener(void) { mFrameListener = new MyFrameListener(mWindow, mCamera, mSceneMgr); mRoot->addFrameListener(mFrameListener); } }; /// End of Application Class #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) #else int main(int argc, char **argv) #endif { // Create application object Application app; try { app.go(); } catch( Exception& e ) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occured: %s\n", e.getFullDescription().c_str()); #endif } return 0; }
and then add this header file, naming it FrameListener.h:-
#ifndef FrameListener_H_ #define FrameListener_H_ #include "Ogre.h" //#include "OgreFrameListener.h" #include "OgreKeyEvent.h" #include "OgreEventListeners.h" #include "OgreStringConverter.h" #include "OgreException.h" using namespace Ogre; class MyFrameListener : public FrameListener, public KeyListener, public MouseListener, public MouseMotionListener { public: // ctor/dtor MyFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr, bool useBufferedInputKeys = false, bool useBufferedInputMouse = false) { mCamera = cam; mWindow = win; mUseBufferedInputKeys = useBufferedInputKeys; mUseBufferedInputMouse = useBufferedInputMouse; mInputTypeSwitchingOn = mUseBufferedInputKeys || mUseBufferedInputMouse; //m_timeElapsed = 0.0f; // key and mouse state tracking mMouseDown = false; mToggle = 0.0; // Populate the camera and scene manager containers mCamNode = cam->getParentSceneNode( )->getParentSceneNode( ); mSceneMgr = sceneMgr; // set the rotation and move speed mRotate = 0.13; mMove = 250; if(mInputTypeSwitchingOn) { mEventProcessor = new EventProcessor; mEventProcessor->initialise(win); mEventProcessor->startProcessingEvents(); mEventProcessor->addKeyListener(this); mEventProcessor->addMouseListener(this); mEventProcessor->addMouseMotionListener(this); mInputDevice = mEventProcessor->getInputReader(); /* Overlay* OverCursor = OverlayManager::getSingleton().create("myCursor"); mCursor = new CursorOverlayElement("myCursor"); mCursor->initialise(); mCursor->setMaterialName("Cursor/default"); mCursor->setDimensions( 32.0/win->getWidth(), 32.0/win->getHeight()); */ } else { mInputDevice= PlatformManager::getSingleton().createInputReader(); mInputDevice->initialise(win,true,true); } } virtual ~MyFrameListener() { } // We will provide some meat to this method override bool frameStarted(const FrameEvent &evt); // We do not need to provide a body for either of these methods, since // Ogre provides a default implementation that does just this. However, for // the sake of illustration, we'll provide one here. virtual bool frameEnded(const FrameEvent &evt) { return true; } // MouseDragged void mouseMoved(MouseEvent* e) { } void mouseDragged(MouseEvent* e) { } // MouseListener void mouseClicked(MouseEvent* e) { } void mouseEntered(MouseEvent* e) { } void mouseExited(MouseEvent* e) { } void mousePressed(MouseEvent* e) { } void mouseReleased(MouseEvent* e) { } // KeyListener virtual void keyClicked(KeyEvent* e) { } virtual void keyPressed(KeyEvent* e) { } virtual void keyReleased(KeyEvent* e) { } protected: //float m_timeElapsed; bool mMouseDown; // Whether or not the left mouse button was down last frame Real mToggle; // The time left until next toggle Real mRotate; // The rotate constant Real mMove; // The movement constant SceneManager *mSceneMgr; // The current SceneManager SceneNode *mCamNode; // The SceneNode the camera is currently attached to Camera* mCamera; RenderWindow* mWindow; InputReader* mInputDevice; //Copied from ExampleFrameListener EventProcessor* mEventProcessor; bool mUseBufferedInputKeys; bool mUseBufferedInputMouse; bool mInputTypeSwitchingOn; //CursorOverlayElement* mCursor; // Copied from PagingLandScape2FrameListener String mCurrentTexture; // From Samples/Paginglandscape2include/FrameListener }; bool MyFrameListener::frameStarted(const FrameEvent &evt) { mInputDevice->capture(); /// Section to move camera based on Key clicks & mouse clicks Vector3 transVector = Vector3::ZERO; if ( mInputDevice->isKeyDown( KC_UP ) || mInputDevice->isKeyDown( KC_W ) ) transVector.z -= mMove; if ( mInputDevice->isKeyDown( KC_DOWN ) || mInputDevice->isKeyDown( KC_S ) ) transVector.z += mMove; if ( mInputDevice->isKeyDown( KC_LEFT ) || mInputDevice->isKeyDown( KC_A ) ) transVector.x -= mMove; if ( mInputDevice->isKeyDown( KC_RIGHT ) || mInputDevice->isKeyDown( KC_D ) ) transVector.x += mMove; if ( mInputDevice->isKeyDown( KC_PGUP ) || mInputDevice->isKeyDown( KC_Q ) ) transVector.y += mMove; if ( mInputDevice->isKeyDown( KC_PGDOWN ) || mInputDevice->isKeyDown( KC_E ) ) transVector.y -= mMove; mCamNode->translate( mCamNode->getOrientation() * transVector * evt.timeSinceLastFrame ); mCamNode->translate( mCamNode->getOrientation() * mCamNode->getChild(0)->getOrientation() * transVector * evt.timeSinceLastFrame ); if ( mInputDevice->getMouseButton( 0 ) ) { mCamNode->yaw( Degree(-mRotate * mInputDevice->getMouseRelativeX()) ); mCamNode->getChild( 0 )->pitch( Degree(-mRotate * mInputDevice->getMouseRelativeY()) ); } return ! mInputDevice->isKeyDown( KC_ESCAPE ); } #endif
Build the project but don't run it yet.
2) In the same directory as the excutable copy over paginglandscape2.cfg, plugins_plsm2.cfg & resources_plsm2.cfg from paginglandscape/Samples/Common/bin renaming the latter two plugins.cfg and resources.cfg respectively. Also copy over terrains.cfg from ogrenew.
3) In the project top directory create a new directory and name it "Media". Open it & in it create two other directories named OgreNew_Media and PLSM_Media.
Copy the contents of ogrenew/Samples/Media to OgreNew_Media and the contents of paginglandscape/Samples/Media to PLSM_Media
4) Edit resources.cfg to reflect the current directory structure eg.
# Resource locations to be added to the 'boostrap' path # This also contains the minimum you need to use the Ogre example framework [Bootstrap] Zip=../../Media/OgreNew_Media/packs/OgreCore.zip # Resource locations to be added to the default path [General] FileSystem=../../Media FileSystem=../../Media/OgreNew_Media FileSystem=../../Media/OgreNew_Media/fonts FileSystem=../../Media/OgreNew_Media/materials/programs FileSystem=../../Media/OgreNew_Media/materials/scripts FileSystem=../../Media/OgreNew_Media/materials/textures FileSystem=../../Media/OgreNew_Media/models FileSystem=../../Media/OgreNew_Media/overlays FileSystem=../../Media/OgreNew_Media/particle FileSystem=../../Media/OgreNew_Media/gui Zip=../../Media/OgreNew_Media/packs/cubemap.zip Zip=../../Media/OgreNew_Media/packs/cubemapsJS.zip Zip=../../Media/OgreNew_Media/packs/dragon.zip Zip=../../Media/OgreNew_Media/packs/fresneldemo.zip Zip=../../Media/OgreNew_Media/packs/ogretestmap.zip Zip=../../Media/OgreNew_Media/packs/skybox.zip [PLSM2] FileSystem=../../Media/PLSM_Media FileSystem=../../Media/PLSM_Media/datasrcs FileSystem=../../Media/PLSM_Media/terrains FileSystem=../../Media/PLSM_Media/terrains/datasrcs FileSystem=../../Media/PLSM_Media/terrains/alpes FileSystem=../../Media/PLSM_Media/terrains/alpineRegion FileSystem=../../Media/PLSM_Media/terrains/hf129_3 FileSystem=../../Media/PLSM_Media/terrains/TsmTerrain FileSystem=../../Media/PLSM_Media/terrains/ps_height_1k FileSystem=../../Media/PLSM_Media/terrains/terragen16bits FileSystem=../../Media/PLSM_Media/terrains/gcanyon_height_4k2k FileSystem=../../Media/PLSM_Media/materials FileSystem=../../Media/PLSM_Media/materials/programs FileSystem=../../Media/PLSM_Media/materials/scripts FileSystem=../../Media/PLSM_Media/materials/textures FileSystem=../../Media/PLSM_Media/models FileSystem=../../Media/PLSM_Media/overlays FileSystem=../../Media/PLSM_Media/gui
Run the project.If all has gone according to plan all you should see in the render window is a simple ogre terrain and be able to move the camera.
5) Setup the default map in paginglandscape.cfg to be TsmTerrain.
Find the chooseSceneManager method in Application.cpp and change:-
mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE);
to:-
mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_REAL_FAR);
or try
mSceneMgr = mRoot->createSceneManager("PagingLandScapeSceneManager", "MyOGREsmgr");
In the same file find the createScene method and change:-
mSceneMgr->setWorldGeometry("terrain.cfg");
to:-
mSceneMgr->setWorldGeometry(Ogre::String("paginglandscape2.cfg"));
6) Rebuild & run.
7) I haven't been able to get the grand canyon map to load using this app. Alpes loads but no map is visible. hf_129_3 works OK if you change Vertex Compression & ProgramMorph from yes to no. in its .cfg file. The same applies to ps_height_1k.
Common Problems
This section will discuss what problems can occur durring the course of this tutorial and how to resolve them.