BasicApp.h
#ifndef BASICAPP_H
#define BASICAPP_H

#include <OgreRoot.h>
#include <OgreCamera.h>
#include <OgreViewport.h>
#include <OgreSceneManager.h>
#include <OgreRenderWindow.h>
#include <OgreConfigFile.h>
#include <OgreException.h>
#include <OgreEntity.h>
#include <OgreFrameListener.h>
#include <OgreWindowEventUtilities.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <OgreSceneQuery.h>

#include <OISEvents.h>
#include <OISInputManager.h>
#include <OISKeyboard.h>
#include <OISMouse.h>

#include <CEGUI/CEGUI.h>
#include <CEGUI/RendererModules/Ogre/Renderer.h>

#include <SdkCameraMan.h>

class BasicApp
  : public Ogre::WindowEventListener,
    public Ogre::FrameListener,
    public OIS::KeyListener,
    public OIS::MouseListener
{
public:
  BasicApp();
  ~BasicApp();

  void go();

  enum QueryFlags
  {
    NINJA_MASK = 1 << 0,
    ROBOT_MASK = 1 << 1
  };

private:
  bool mShutdown;

  Ogre::Root* mRoot;
  Ogre::Camera* mCamera;
  Ogre::SceneManager* mSceneMgr;
  Ogre::RenderWindow* mWindow;
  Ogre::String mResourcesCfg;
  Ogre::String mPluginsCfg;

  OgreBites::SdkCameraMan* mCameraMan;

  virtual bool frameRenderingQueued(const Ogre::FrameEvent& fe);

  virtual bool keyPressed(const OIS::KeyEvent& ke);
  virtual bool keyReleased(const OIS::KeyEvent& ke);

  virtual bool mouseMoved(const OIS::MouseEvent& me);
  virtual bool mousePressed(const OIS::MouseEvent& me, OIS::MouseButtonID id);
  virtual bool mouseReleased(const OIS::MouseEvent& me, OIS::MouseButtonID id);

  virtual void windowResized(Ogre::RenderWindow* rw);
  virtual void windowClosed(Ogre::RenderWindow* rw);

  bool setup();
  bool configure();
  void chooseSceneManager();
  void createCamera();
  void createScene();
  void destroyScene();
  void createFrameListener();
  void createViewports();
  void setupResources();
  void createResourceListener();
  void loadResources();

  // CEGUI
  CEGUI::OgreRenderer* mRenderer;

  bool setupCEGUI();

  // OIS
  OIS::Mouse* mMouse;
  OIS::Keyboard* mKeyboard;
  OIS::InputManager* mInputMgr;

  // Terrain
  void setupTerrain(Ogre::Light* light);
  void defineTerrain(long x, long y);
  void initBlendMaps(Ogre::Terrain* terrain);
  void configureTerrainDefaults(Ogre::Light* light);

  bool mTerrainsImported;

  Ogre::TerrainGroup* mTerrainGroup;
  Ogre::TerrainGlobalOptions* mTerrainGlobals;

  //////////////////////
  // Tutorial Section //
  //////////////////////
  void handleCameraCollision();

  float mRotSpd;
  bool mMovableFound, mRobotMode;
  bool mLMouseDown, mRMouseDown;

  Ogre::SceneNode* mCurObject;
  Ogre::RaySceneQuery* mRayScnQuery;
};

#endif

BasicApp.cpp
#include "BasicApp.h"

BasicApp::BasicApp()
  : mShutdown(false),
    mRoot(0),
    mCamera(0),
    mSceneMgr(0),
    mWindow(0),
    mResourcesCfg(Ogre::StringUtil::BLANK),
    mPluginsCfg(Ogre::StringUtil::BLANK),
    mCameraMan(0),
    mRenderer(0),
    mMouse(0),
    mKeyboard(0),
    mInputMgr(0),
    mTerrainsImported(false),
    mTerrainGroup(0),
    mTerrainGlobals(0),
    mRotSpd(0.1),
    mRobotMode(true),
    mLMouseDown(false),
    mRMouseDown(false),
    mCurObject(0),
    mRayScnQuery(0)
{
}

BasicApp::~BasicApp()
{
  if (mCameraMan) delete mCameraMan;

  mSceneMgr->destroyQuery(mRayScnQuery);

  Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this);
  windowClosed(mWindow);

  delete mRoot;
}

void BasicApp::go()
{
  #ifdef _DEBUG
    mResourcesCfg = "resources_d.cfg";
    mPluginsCfg = "plugins_d.cfg";
  #else
    mResourcesCfg = "resources.cfg";
    mPluginsCfg = "plugins.cfg";
  #endif

  if (!setup())
    return;

  mRoot->startRendering();

  destroyScene();
}

bool BasicApp::frameRenderingQueued(const Ogre::FrameEvent& fe)
{
  if (mKeyboard->isKeyDown(OIS::KC_ESCAPE))
    mShutdown = true;

  if (mShutdown)
    return false;

  if (mWindow->isClosed())
    return false;

  mKeyboard->capture();
  mMouse->capture();

  mCameraMan->frameRenderingQueued(fe);

  CEGUI::System::getSingleton().injectTimePulse(fe.timeSinceLastFrame);

  CEGUI::Window* terrainLabel =
    CEGUI::System::getSingleton().getDefaultGUIContext().getRootWindow()->getChild(
      "TerrainLabel");

  if (mTerrainGroup->isDerivedDataUpdateInProgress())
  {
    if (mTerrainsImported)
      terrainLabel->setText("Building terrain, please wait...");
    else
      terrainLabel->setText("Updating textures, patience...");
  }
  else
  {
    if (mRobotMode)
      terrainLabel->setText("Robot Mode");
    else
      terrainLabel->setText("Ninja Mode");

    if (mTerrainsImported)
    {
      mTerrainGroup->saveAllTerrains(true);
      mTerrainsImported = false;
    }
  }

  handleCameraCollision();

  return true;
}

bool BasicApp::keyPressed(const OIS::KeyEvent& ke)
{
  // CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
  // context.injectKeyDown((CEGUI::Key::Scan)ke.key);
  // context.injectChar((CEGUI::Key::Scan)ke.text);

  if (ke.key == OIS::KC_SPACE)
    mRobotMode = !mRobotMode;

  mCameraMan->injectKeyDown(ke);

  return true;
}

bool BasicApp::keyReleased(const OIS::KeyEvent& ke)
{
  // CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
  // context.injectKeyUp((CEGUI::Key::Scan)ke.key);

  mCameraMan->injectKeyUp(ke);

  return true;
}

bool BasicApp::mouseMoved(const OIS::MouseEvent& me)
{
  CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
  context.injectMouseMove(me.state.X.rel, me.state.Y.rel);

  // mCameraMan->injectMouseMove(me);

  if (mLMouseDown)
  {
    CEGUI::Vector2f mousePos = context.getMouseCursor().getPosition();

    Ogre::Ray mouseRay =
      mCamera->getCameraToViewportRay(
	mousePos.d_x / float(me.state.width),
	mousePos.d_y / float(me.state.height));

    Ogre::TerrainGroup::RayResult result = mTerrainGroup->rayIntersects(mouseRay);

    if (result.terrain)
      mCurObject->setPosition(result.position);
  }
  else if (mRMouseDown)
  {
    mCamera->yaw(Ogre::Degree(-me.state.X.rel * mRotSpd));
    mCamera->pitch(Ogre::Degree(-me.state.Y.rel * mRotSpd));
  }

  return true;
}

// Helper function for mouse events
CEGUI::MouseButton convertButton(OIS::MouseButtonID id)
{
  switch (id)
  {
  case OIS::MB_Left:
    return CEGUI::LeftButton;
  case OIS::MB_Right:
    return CEGUI::RightButton;
  case OIS::MB_Middle:
    return CEGUI::MiddleButton;
  default:
    return CEGUI::LeftButton;
  }
}

bool BasicApp::mousePressed(const OIS::MouseEvent& me, OIS::MouseButtonID id)
{
  CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
  context.injectMouseButtonDown(convertButton(id));

  // mCameraMan->injectMouseDown(me, id);

  if (id == OIS::MB_Left)
  {
    if (mCurObject)
      mCurObject->showBoundingBox(false);

    CEGUI::Vector2f mousePos = context.getMouseCursor().getPosition();

    Ogre::Ray mouseRay =
      mCamera->getCameraToViewportRay(
	mousePos.d_x / float(me.state.width),
	mousePos.d_y / float(me.state.height));

    mRayScnQuery->setRay(mouseRay);
    mRayScnQuery->setSortByDistance(true);
    mRayScnQuery->setQueryMask(mRobotMode ? ROBOT_MASK : NINJA_MASK);

    Ogre::RaySceneQueryResult& result = mRayScnQuery->execute();
    Ogre::RaySceneQueryResult::iterator it = result.begin();

    mMovableFound = false;

    for ( ; it != result.end(); it++)
    {
      mMovableFound =
	it->movable &&
	it->movable->getName() != "" &&
	it->movable->getName() != "PlayerCam";

      if (mMovableFound)
      {
	mCurObject = it->movable->getParentSceneNode();
	break;
      }
    }

    if (!mMovableFound)
    {
      Ogre::TerrainGroup::RayResult terrainResult =
	mTerrainGroup->rayIntersects(mouseRay);

      if (terrainResult.terrain)
      {
	Ogre::Entity* ent;

	if (mRobotMode)
	{
	  ent = mSceneMgr->createEntity("robot.mesh");
	  ent->setQueryFlags(ROBOT_MASK);
	}
	else
	{
	  ent = mSceneMgr->createEntity("ninja.mesh");
	  ent->setQueryFlags(NINJA_MASK);
	}

	mCurObject = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	mCurObject->setPosition(terrainResult.position);
	mCurObject->setScale(0.2, 0.2, 0.2);
	mCurObject->attachObject(ent);
      }
    }

    mLMouseDown = true;
  }
  else if (id == OIS::MB_Right)
  {
    context.getMouseCursor().hide();

    mRMouseDown = true;
  }

  if (mCurObject)
    mCurObject->showBoundingBox(true);

  return true;
}

bool BasicApp::mouseReleased(const OIS::MouseEvent& me, OIS::MouseButtonID id)
{
  CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
  context.injectMouseButtonUp(convertButton(id));

  // mCameraMan->injectMouseUp(me, id);

  if (id == OIS::MB_Left)
  {
    mLMouseDown = false;
  }
  else if (id == OIS::MB_Right)
  {
    context.getMouseCursor().show();
    mRMouseDown = false;
  }

  return true;
}

void BasicApp::windowResized(Ogre::RenderWindow* rw)
{
  unsigned int width, height, depth;
  int left, top;
  rw->getMetrics(width, height, depth, left, top);

  const OIS::MouseState& ms = mMouse->getMouseState();
  ms.width = width;
  ms.height = height;
}

void BasicApp::windowClosed(Ogre::RenderWindow* rw)
{
  if (rw == mWindow)
  {
    if (mInputMgr)
    {
      mInputMgr->destroyInputObject(mMouse);
      mInputMgr->destroyInputObject(mKeyboard);

      OIS::InputManager::destroyInputSystem(mInputMgr);
      mInputMgr = 0;
    }
  }
}

bool BasicApp::setup()
{
  mRoot = new Ogre::Root(mPluginsCfg);

  setupResources();

  if (!configure())
    return false;

  chooseSceneManager();
  createCamera();
  createViewports();

  Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

  createResourceListener();
  loadResources();

  setupCEGUI();

  createScene();

  createFrameListener();

  mRayScnQuery = mSceneMgr->createRayQuery(Ogre::Ray());

  return true;
}

bool BasicApp::configure()
{
  if (!(mRoot->restoreConfig() || mRoot->showConfigDialog()))
  {
    return false;
  }

  mWindow = mRoot->initialise(true, "ITutorial");

  return true;
}

void BasicApp::chooseSceneManager()
{
  mSceneMgr = mRoot->createSceneManager(Ogre::ST_EXTERIOR_CLOSE);
}

void BasicApp::createCamera()
{
  mCamera = mSceneMgr->createCamera("PlayerCam");

  mCamera->setPosition(Ogre::Vector3(0, 0, 80));
  mCamera->lookAt(Ogre::Vector3(0, 0, -300));
  mCamera->setNearClipDistance(5);

  mCameraMan = new OgreBites::SdkCameraMan(mCamera);
}

void BasicApp::createScene()
{
  mSceneMgr->setAmbientLight(Ogre::ColourValue(0.7, 0.7, 0.7));
  mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);

  mCamera->setPosition(40, 100, 580);
  mCamera->pitch(Ogre::Degree(-30));
  mCamera->yaw(Ogre::Degree(-45));
  mCamera->setNearClipDistance(0.1);
  mCamera->setFarClipDistance(50000);

  Ogre::Vector3 lightDir(0.55, 0.3, 0.75);
  lightDir.normalise();

  Ogre::Light* light = mSceneMgr->createLight("SceneLight");
  light->setType(Ogre::Light::LT_DIRECTIONAL);
  light->setDirection(lightDir);
  light->setDiffuseColour(Ogre::ColourValue(0.4, 0.4, 0.4));
  light->setSpecularColour(Ogre::ColourValue(0.2, 0.2, 0.2));

  setupTerrain(light);

  CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
  CEGUI::Window* rootWin = wmgr.loadLayoutFromFile("test.layout");

  CEGUI::System::getSingleton().getDefaultGUIContext().setRootWindow(rootWin);

}

void BasicApp::destroyScene()
{
}

void BasicApp::createFrameListener()
{
  Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");

  OIS::ParamList pl;
  size_t windowHnd = 0;
  std::ostringstream windowHndStr;

  mWindow->getCustomAttribute("WINDOW", &windowHnd);
  windowHndStr << windowHnd;
  pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

  mInputMgr = OIS::InputManager::createInputSystem(pl);

  mKeyboard = static_cast<OIS::Keyboard*>(
    mInputMgr->createInputObject(OIS::OISKeyboard, true));
  mMouse = static_cast<OIS::Mouse*>(
    mInputMgr->createInputObject(OIS::OISMouse, true));

  mKeyboard->setEventCallback(this);
  mMouse->setEventCallback(this);

  windowResized(mWindow);

  Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);

  mRoot->addFrameListener(this);

  Ogre::LogManager::getSingletonPtr()->logMessage("Finished");
}

void BasicApp::createViewports()
{
  Ogre::Viewport* vp = mWindow->addViewport(mCamera);
  vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0));

  mCamera->setAspectRatio(
    Ogre::Real(vp->getActualWidth()) /
    Ogre::Real(vp->getActualHeight()));
}

void BasicApp::setupResources()
{
  Ogre::ConfigFile cf;
  cf.load(mResourcesCfg);

  Ogre::String secName, typeName, archName;
  Ogre::ConfigFile::SectionIterator secIt = cf.getSectionIterator();

  while (secIt.hasMoreElements())
  {
    secName = secIt.peekNextKey();
    Ogre::ConfigFile::SettingsMultiMap* settings = secIt.getNext();
    Ogre::ConfigFile::SettingsMultiMap::iterator setIt;

    for (setIt = settings->begin(); setIt != settings->end(); ++setIt)
    {
      typeName = setIt->first;
      archName = setIt->second;
      Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
	archName, typeName, secName);
    }
  }
}

void BasicApp::createResourceListener()
{
}

void BasicApp::loadResources()
{
  Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}

bool BasicApp::setupCEGUI()
{
  Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing CEGUI ***");

  mRenderer = &CEGUI::OgreRenderer::bootstrapSystem();

  CEGUI::ImageManager::setImagesetDefaultResourceGroup("Imagesets");
  CEGUI::Font::setDefaultResourceGroup("Fonts");
  CEGUI::Scheme::setDefaultResourceGroup("Schemes");
  CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");
  CEGUI::WindowManager::setDefaultResourceGroup("Layouts");

  CEGUI::SchemeManager::getSingleton().createFromFile("TaharezLook.scheme");
  CEGUI::FontManager::getSingleton().createFromFile("DejaVuSans-10.font");

  CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();

  context.setDefaultFont("DejaVuSans-10");
  context.getMouseCursor().setDefaultImage("TaharezLook/MouseArrow");

  Ogre::LogManager::getSingletonPtr()->logMessage("Finished");

  return true;
}

void BasicApp::setupTerrain(Ogre::Light* light)
{
  mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
  mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(
    mSceneMgr,
    Ogre::Terrain::ALIGN_X_Z, 513, 12000.0);
  mTerrainGroup->setFilenameConvention(
    Ogre::String("terrain"),
    Ogre::String("dat"));
  mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);

  configureTerrainDefaults(light);

  for (long x = 0; x <= 0; ++x)
    for (long y = 0; y <= 0; ++y)
      defineTerrain(x, y);

  mTerrainGroup->loadAllTerrains(true);

  if (mTerrainsImported)
  {
    Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();

    while (ti.hasMoreElements())
    {
      Ogre::Terrain* terrain = ti.getNext()->instance;
      initBlendMaps(terrain);
    }
  }

  mTerrainGroup->freeTemporaryResources();
}

// Terrain helper function
void getTerrainImage(bool flip_x, bool flip_y, Ogre::Image& img)
{
  img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

  if (flip_x)
    img.flipAroundY();
  if (flip_y)
    img.flipAroundX();
}

void BasicApp::defineTerrain(long x, long y)
{
  Ogre::String filename = mTerrainGroup->generateFilename(x, y);

  bool resource_exists =
    Ogre::ResourceGroupManager::getSingleton().resourceExists(
      mTerrainGroup->getResourceGroup(), filename);

  if (resource_exists)
    mTerrainGroup->defineTerrain(x, y);
  else
  {
    Ogre::Image img;
    getTerrainImage(x % 2 != 0, y % 2 != 0, img);
    mTerrainGroup->defineTerrain(x, y, &img);
    mTerrainsImported = true;
  }
}

void BasicApp::initBlendMaps(Ogre::Terrain* terrain)
{
  Ogre::TerrainLayerBlendMap* blendMap0 = terrain->getLayerBlendMap(1);
  Ogre::TerrainLayerBlendMap* blendMap1 = terrain->getLayerBlendMap(2);
  Ogre::Real minHeight0 = 70;
  Ogre::Real fadeDist0 = 40;
  Ogre::Real minHeight1 = 70;
  Ogre::Real fadeDist1 = 15;
  float* blend0 = blendMap0->getBlendPointer();
  float* blend1 = blendMap1->getBlendPointer();

  for (Ogre::uint16 y = 0; y < terrain->getLayerBlendMapSize(); ++y)
  {
    for (Ogre::uint16 x = 0; x < terrain->getLayerBlendMapSize(); ++x)
    {
      Ogre::Real tx, ty;

      blendMap0->convertImageToTerrainSpace(x, y, &tx, &ty);
      Ogre::Real height = terrain->getHeightAtTerrainPosition(tx, ty);
      Ogre::Real val = (height - minHeight0) / fadeDist0;
      val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
      *blend0++ = val;

      val = (height - minHeight1) / fadeDist1;
      val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
      *blend1++ = val;
    }
  }

  blendMap0->dirty();
  blendMap1->dirty();
  blendMap0->update();
  blendMap1->update();
}

void BasicApp::configureTerrainDefaults(Ogre::Light* light)
{
  mTerrainGlobals->setMaxPixelError(8);
  mTerrainGlobals->setCompositeMapDistance(3000);
  mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
  mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
  mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());

  Ogre::Terrain::ImportData& importData = mTerrainGroup->getDefaultImportSettings();
  importData.terrainSize = 513;
  importData.worldSize = 12000.0f;
  importData.inputScale = 600;
  importData.minBatchSize = 33;
  importData.maxBatchSize = 65;

  importData.layerList.resize(3);
  importData.layerList[0].worldSize = 100;
  importData.layerList[0].textureNames.push_back(
    "dirt_grayrocky_diffusespecular.dds");
  importData.layerList[0].textureNames.push_back(
    "dirt_grayrocky_normalheight.dds");
  importData.layerList[1].worldSize = 30;
  importData.layerList[1].textureNames.push_back(
    "grass_green-01_diffusespecular.dds");
  importData.layerList[1].textureNames.push_back(
    "grass_green-01_normalheight.dds");
  importData.layerList[2].worldSize = 200;
  importData.layerList[2].textureNames.push_back(
    "growth_weirdfungus-03_diffusespecular.dds");
  importData.layerList[2].textureNames.push_back(
    "growth_weirdfungus-03_normalheight.dds");
}

void BasicApp::handleCameraCollision()
{
  Ogre::Vector3 camPos = mCamera->getPosition();
  Ogre::Ray camRay(
    Ogre::Vector3(camPos.x, 5000.0, camPos.z),
    Ogre::Vector3::NEGATIVE_UNIT_Y);

  Ogre::TerrainGroup::RayResult result = mTerrainGroup->rayIntersects(camRay);

  if (result.terrain)
  {
    Ogre::Real terrainHeight = result.position.y;

    if (camPos.y < (terrainHeight + 10.0))
      mCamera->setPosition(camPos.x, terrainHeight + 10.0, camPos.z);
  }
}


#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
  #define WIN32_LEAN_AND_MEAN
  #include "windows.h"
#endif

#ifdef __cplusplus
  extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
  INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
  int main(int argc, char *argv[])
#endif
{
  BasicApp app;

  try
  {
    app.go();
  }
  catch(Ogre::Exception& e)
  {
    #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
      MessageBox(
	NULL,
	e.getFullDescription().c_str(),
	"An exception has occured!",
	MB_OK | MB_ICONERROR | MB_TASKMODAL);
    #else
      std::cerr << "An exception has occured: " <<
	e.getFullDescription().c_str() << std::endl;
    #endif
  }

  return 0;
}

#ifdef __cplusplus
  }
#endif