Wednesday, October 28, 2009

Irrlicht - How to get mouse pointer coordinates on a terrain?

You may need the coordinates where a user clicked on your terrain for many reasons. For example many MMORPG games use the mouse to move the character. Using point-and-click system is relatively easy for the players and not hard to implement as a code.

How to get mouse coordinates from a 3D place with Irrlicht? You need to use getCollisionPoint member function of the irr::scene::ISceneCollisionManager class. The current documentation at the official site is for version 1.5.1 but the current Irrlicht version is 1.6. getCollisionPoint function has one more parameter in the current version.
If you try to compile the project with only four parameters you’ll get an error message:

**** Build of configuration Debug for project GameNext ****

**** Internal Builder is used for build ****
g++ -ID:\irrlicht-1.6\include -O0 -g3 -Wall -c -fmessage-length=0 -osrc\GameNext.o ..\src\GameNext.cpp
..\src\GameNext.cpp: In function `int main()':
..\src\GameNext.cpp:59: error: no matching function for call to `irr::scene::ISceneCollisionManager::getCollisionPoint(const irr::core::line3d<irr::f32>&, irr::scene::ITriangleSelector*&, irr::core::vector3df&, irr::core::triangle3df&)'
D:/irrlicht-1.6/include/ISceneCollisionManager.h:43: note: candidates are: virtual bool irr::scene::ISceneCollisionManager::getCollisionPoint(const irr::core::line3d<irr::f32>&, irr::scene::ITriangleSelector*, irr::core::vector3df&, irr::core::triangle3df&, const irr::scene::ISceneNode*&)
..\src\GameNext.cpp:57: warning: unused variable 'node'
Build error occurred, build is stopped
Time consumed: 1873 ms.

The right syntax is:

bool getCollisionPoint(const core::line3d<f32>& ray,
        ITriangleSelector* selector, core::vector3df& outCollisionPoint,
        core::triangle3df& outTriangle, const ISceneNode*& outNode)


So how would our code look like?

First we need to get the cursor position in the 2D space.
core::position2d< s32 > pos = cursor->getPosition();
Now, we need to get the 3D vector from it.
const core::line3d<f32> ray = 
        collisionManager->getRayFromScreenCoordinates(pos);
And check if our line intersects with the terrain.
if(collisionManager->getCollisionPoint
        (ray, selector, point, triangle, node))

{
 // do something
}

Here is the full demo code:

#include <irrlicht.h>

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;

using namespace io;
using namespace gui;

int main() {

 IrrlichtDevice *device = createDevice(video::EDT_OPENGL,
   dimension2d<u32> (800, 600),
   16, false, false, false, 0);

 if (!device)
  return 1;

 IVideoDriver* driver = device->getVideoDriver();

 ISceneManager* sceneManager = device->getSceneManager();
 IGUIEnvironment* guiEnvironment = device->getGUIEnvironment();

 // -------
 scene::ITerrainSceneNode* currentTerrain =
   sceneManager->addTerrainSceneNode(

     "media/poiheightmap.bmp", // height map
     0, // parent node
     -1, // node id
     core::vector3df(0.f, 0.f, 0.f), // position

     core::vector3df(0.f, 0.f, 0.f), // rotation
     core::vector3df(100.f, 1.f, 100.f), // scale

     video::SColor(255, 255, 255, 255), // vertexColor

     5, // maxLOD
     scene::ETPS_17, // patchSize
     4);

 currentTerrain->setMaterialFlag(video::EMF_LIGHTING, false);
 currentTerrain->setMaterialTexture(0,
   driver->getTexture("media/terrain-texture.jpg"));

 currentTerrain->setMaterialTexture(1,
   driver->getTexture("media/detailmap3.jpg"));
 currentTerrain->scaleTexture(1.0f, 20.0f);

 ITriangleSelector* selector = sceneManager->
   createTerrainTriangleSelector(currentTerrain);

 currentTerrain->setTriangleSelector(selector);

 // retrieve the SceneCollisionManager object
 ISceneCollisionManager* collisionManager =

   sceneManager->getSceneCollisionManager();

 // get cursor
 ICursorControl *cursor = device->getCursorControl();

 // simple camera with position and target
 sceneManager->addCameraSceneNode(0,
   core::vector3df(100.f, 60.f, 100.f),
   core::vector3df(900.f, 0.f, 900.f));

 // this sphere will mark our collision point
 ISceneNode* sphere = sceneManager->addSphereSceneNode();

 while (device->run()) {
  core::position2d<s32> pos = cursor->getPosition();

  core::vector3df point;
  core::triangle3df triangle;

  const ISceneNode *node = 0;
  const core::line3d<f32> ray = collisionManager->

    getRayFromScreenCoordinates(pos);
  if (collisionManager->getCollisionPoint
    (ray, selector, point, triangle, node))

  {
   sphere->setPosition(point);
  }

  driver->beginScene(true, true, SColor(255, 50, 130, 50));

  sceneManager->drawAll();
  guiEnvironment->drawAll();
  driver->endScene();

 }

 selector->drop();

 device->drop();

 return 0;
}

No comments: