Creating a PLSM Application        

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.