2008年12月23日

Ogre Wiki - Intermediate Tutorials 3

というわけで、Ogre WikiのIntermediate Tutorials 3です。

かなり時間があいてしまったので良く覚えていませんが、今後も気が向いたら4,5,6,7と進めていきたいと思います。

実行画面

ogre_intermediate_tutorial_003.JPG

Intermediate Tutorial 3: マウスピッキング(3Dオブジェクト選択)とSceneQueryマスク

このチュートリアルで何か問題があった場合は、ヘルプフォーラムで聞いてください。
導入

このチュートリアルでは、私たちは前回のチュートリアルの続きを作業します。ここでは、マウスを使ってスクリーン上のオブジェクトをどのように選択し、どれを選択可能にするかなどの方法を説明します。

ソースコードはここにあります。

要求

このチュートリアルでは、前回のチュートリアルを行っていることを想定しています。また、STLのイテレータをSceneQueriesの結果として使用するので、それらの基本的な知識があれば助けになるでしょう。

開始

私たちは前回からコードを編集したので、少し読みやすくなったと思います。全てのマウスイベントのために、それらを扱うためのラッパー関数を作成しました。ユーザが左クリックをした場合は"onLeftPressed"関数が呼ばれ、右ボタンを離した場合は"onRightReleased"関数が呼ばれます。あなた
はこれらの変更を、チュートリアルが始まる前に見ておくべきでしょう。

新しくnew.cppファイルを作成してあなたのソリューションに追加し、下記のコードを入力してください:
#include <CEGUI/CEGUISystem.h>
#include <CEGUI/CEGUISchemeManager.h>
#include <OgreCEGUIRenderer.h>

#include "ExampleApplication.h"

class MouseQueryListener : public ExampleFrameListener, public OIS::MouseListener
{
public:

MouseQueryListener(RenderWindow* win, Camera* cam, SceneManager *sceneManager, CEGUI::Renderer *renderer)
: ExampleFrameListener(win, cam, false, true), mGUIRenderer(renderer)
{
// Setup default variables
mCount = 0;
mCurrentObject = NULL;
mLMouseDown = false;
mRMouseDown = false;
mSceneMgr = sceneManager;

// Reduce move speed
mMoveSpeed = 50;
mRotateSpeed /= 500;

// Register this so that we get mouse events.
mMouse->setEventCallback(this);

// Create RaySceneQuery
mRaySceneQuery = mSceneMgr->createRayQuery(Ray());
} // MouseQueryListener

~MouseQueryListener()
{
mSceneMgr->destroyQuery(mRaySceneQuery);
}

bool frameStarted(const FrameEvent &evt)
{
// Process the base frame listener code. Since we are going to be
// manipulating the translate vector, we need this to happen first.
if (!ExampleFrameListener::frameStarted(evt))
return false;

// Setup the scene query
Vector3 camPos = mCamera->getPosition();
Ray cameraRay(Vector3(camPos.x, 5000.0f, camPos.z), Vector3::NEGATIVE_UNIT_Y);
mRaySceneQuery->setRay(cameraRay);

// Perform the scene query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();

// Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{
Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );
}

return true;
}

/* MouseListener callbacks. */
bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
// Left mouse button up
if (id == OIS::MB_Left)
{
onLeftReleased(arg);
mLMouseDown = false;
} // if

// Right mouse button up
else if (id == OIS::MB_Right)
{
onRightReleased(arg);
mRMouseDown = false;
} // else if

return true;
}

bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
// Left mouse button down
if (id == OIS::MB_Left)
{
onLeftPressed(arg);
mLMouseDown = true;
} // if

// Right mouse button down
else if (id == OIS::MB_Right)
{
onRightPressed(arg);
mRMouseDown = true;
} // else if

return true;
}

bool mouseMoved(const OIS::MouseEvent &arg)
{
// Update CEGUI with the mouse motion
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);

// If we are dragging the left mouse button.
if (mLMouseDown)
{
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width),mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);

RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();

if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
} // if

// If we are dragging the right mouse button.
else if (mRMouseDown)
{
mCamera->yaw(Degree(-arg.state.X.rel * mRotateSpeed));
mCamera->pitch(Degree(-arg.state.Y.rel * mRotateSpeed));
} // else if

return true;
}

// Specific handlers
void onLeftPressed(const OIS::MouseEvent &arg)
{
// Setup the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);

// Execute query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin( );

// Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
char name[16];
sprintf(name, "Robot%d", mCount++);

Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
}

void onLeftReleased(const OIS::MouseEvent &arg)
{
}

void onRightPressed(const OIS::MouseEvent &arg)
{
CEGUI::MouseCursor::getSingleton().hide();
}

virtual void onRightReleased(const OIS::MouseEvent &arg)
{
CEGUI::MouseCursor::getSingleton().show();
}

protected:
RaySceneQuery *mRaySceneQuery; // The ray scene query pointer
bool mLMouseDown, mRMouseDown; // True if the mouse buttons are down
int mCount; // The number of robots on the screen
SceneManager *mSceneMgr; // A pointer to the scene manager
SceneNode *mCurrentObject; // The newly created object
CEGUI::Renderer *mGUIRenderer; // CEGUI renderer
};

class MouseQueryApplication : public ExampleApplication
{
protected:
CEGUI::OgreCEGUIRenderer *mGUIRenderer;
CEGUI::System *mGUISystem; // CEGUI system
public:
MouseQueryApplication()
{
}

~MouseQueryApplication()
{
}
protected:
void chooseSceneManager(void)
{
// Use the terrain scene manager.
mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE);
}

void createScene(void)
{
// Set ambient light
mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);

// World geometry
mSceneMgr->setWorldGeometry("terrain.cfg");

// Set camera look point
mCamera->setPosition(40, 100, 580);
mCamera->pitch(Degree(-30));
mCamera->yaw(Degree(-45));

// CEGUI setup
mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
mGUISystem = new CEGUI::System(mGUIRenderer);

// Mouse
CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");
}

void createFrameListener(void)
{
mFrameListener = new MouseQueryListener(mWindow, mCamera, mSceneMgr, mGUIRenderer);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};


#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
MouseQueryApplication app;

try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s\n",
e.getFullDescription().c_str());
#endif
}

return 0;
}
次に進む前に、コンパイルして実行できることを確認してください。コードは多少変更されていますが、前のチュートリアルと同じ動きをするはずです。

選択されたオブジェクトの表示

このチュートリアルでは、あなたがオブジェクトを"ピックアップ"した後、それらを置くことができるようにします。私たちは、ユーザが現在どのオブジェクトを操作しているのかわかるようにしたいと思います。ゲームでは、オブジェクトをハイライトする特殊な方法を作ることが多いと思いますが、このチュートリアルでは(そしてリリース準備前のあなたのアプリケーションでは)オブジェクトの周りにボックスを作成するshowBoundingBox関数を使用することができます。

私たちの基本的な考えは、最初にマウスでクリックした時に、現在まで選択されていたオブジェクトのバウンディングボックスを無効にして、その後新しいオブジェクトのバウンディングボックスを有効にします。これをやるために、私たちはonLeftPressed関数の最初に、次のコードを追加します:
       // Turn off bounding box.
if (mCurrentObject)
mCurrentObject->showBoundingBox(false);
その後、次のコードをonLeftPressed関数の最後に追加します。
       // Show the bounding box to highlight the selected object
if (mCurrentObject)
mCurrentObject->showBoundingBox(true);
これで、mCurrentObjectはスクリーン上でいつも強調されます。

忍者の追加

私たちはロボットだけサポートするのではなく、忍者も配置して動かせるようにコードを修正したいと思います。私たちは"Robot Mode"と"Ninja Mode"を定義し、スクリーン上に配置したいと思います。モードの切り替えはスペースキーで行い、ユーザに現在のモードをメッセージとして表示したいと思います。

最初に、私たちはロボットモードのMouseQueryListenerの設定から開始したいと思います。私たちはオブジェクトの状態を保持する変数を追加する必要があります(それは、私たちがロボットと忍者のどちらを配置するのかという)。MouseQueryListenerのprotectedセクションに移動して、この変数を追加してください:
    bool mRobotMode;                   // The current state
MouseQueryListenerのコンストラクタの最後にこのコードを追加してください:
        // Set result text, and default state
mRobotMode = true;
mDebugText = "Robot Mode Enabled - Press Space to Toggle";
これでロボットモードになりました!とてもシンプルです。次に、私たちはロボットか忍者メッシュを、mRotobMode変数に基づいて作成する必要があります。次のコードをonLeftPressed関数に追加してください:
           char name[16];
sprintf(name, "Robot%d", mCount++);

Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
再配置は次のようになります。mRotobModeの状態に従って、ロボットか忍者を配置します:
           Entity *ent;
char name[16];

if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
これでほとんどの作業は行いました。残りはスペースバーで状態を変更することです。frameStarted関数の次のコードを探してください:
        if (!ExampleFrameListener::frameStarted(evt))
return false;
そしてこのコードの後に、以下のコードを追加してください:
        // Swap modes
if(mKeyboard->isKeyDown(OIS::KC_SPACE) && mTimeUntilNextToggle <= 0)
{
mRobotMode = !mRobotMode;
mTimeUntilNextToggle = 1;
mDebugText = (mRobotMode ? String("Robot") : String("Ninja")) + " Mode Enabled - Press Space to Toggle";
}
これで全てです!コンパイルして実行してください。スペースキーでオブジェクトを選択できるようになりました。

オブジェクトの選択

ではこのチュートリアルの肝に入っていきたいと思います。RaySceneQueriesを使用して、スクリーン上のオブジェクトを選択します。私たちがコードの変更を開始する前に、RaySceneQueryResultEntryの詳細について説明したいと思います(リンク先を見て簡単に構造体を確認して置いてください)。

RaySceneQueryResultは、RaySceneQueryResultEntry構造体のイテレータを返します。この構造体は、3つの変数を持っています。distance変数は、レイからオブジェクトまでがどれくらい離れているかをあなたに教えてくれます。残りの2つのうちの一つはNULLではありません。movable変数は、Rayが交差した場合にMovableObjectを持ちます。worldFragmentは、(地面のような)ワールドフラグメントにレイが当たった場合に、WorldFragmentを持ちます。

MovableObjectsは、基本的には(Entities、Lightsのような)SceneNodeに関連付けられたオブジェクトです。このページの継承ツリーでどのようなオブジェクトが返されるのか見てください。RaySceneQueriesのほとんどの一般的なアプリケーションでは、あなたがクリックしたMovableObjectか、それらが関連付けられているSceneNodeを選択して操作します。MovableObjectの名前を取得するためには、getName関数を呼んでください。オブジェクトに関連付けられているSceneNodeを取得する場合は、getParentSceneNode(またはgetParentNode)関数を呼んでください。RaySceneQueryResultEntryのmovable変数は、結果がMovableObjectではない場合はNULLと等しくなります。

WorldFragmentは少し変わっています。RaySceneQueryResultのworldFragmentメンバがセットされた時、結果がSceneManagerによって作成されたワールドジオメトリの一部であることを意味します。返されるワールドフラグメントの種類は、SceneManagerに基づきます。WorldFragment構造体は、ワールドフラグメントの種類を指定するfragmentType変数を持っているのでこれを使います。fragmentType変数に基づいて、他の変数のうちの一つ(singleIntersection, planes, geometry, or renderOp)が設定されます。一般的には、RaySceneQueriesはWFT_SINGLE_INTERSECTION WorldFragmentsだけを返します。singleIntersection変数は、単純に交差した座標のVector3です。ワールドフラグメントのほかの種類は、このチュートリアルの対象外です。

それでは例を見てみましょう。私たちはRaySceneQueryの実行結果を表示することで説明したいと思います。コードは次のようになります。(ofstreamのfoutオブジェクトが既に使用可能状態であることを想定しています。)
// Do not add this code to the program, just read along:

RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr;

// loop through the results
for ( itr = result.begin( ); itr != result.end(); itr++ )
{
// Is this result a WorldFragment?
if ( itr->worldFragment )
{
Vector3 location = itr->worldFragment->singleIntersection;
fout << "WorldFragment: (" << location.x << ", " << location.y << ", " << location.z << ")" << endl;
} // if

// Is this result a MovableObject?
else if ( itr->movable )
{
fout << "MovableObject: " << itr->movable->getName() << endl;
} // else if
} // for
これは、レイが交差した全てのMovableObjectsの名前をプリントアウトして、(もしそれがワールドジオメトリに当たった場合には)交差したワールドジオメトリの座標を表示します。これは時々奇妙なことが起こることに注意してください。例えば、もしあなたがTerrainSceneManagerを使用していた場合、あなたが飛ばすRayの原点はTerrainより上でなければ、交差クエリはそれを当たったと登録しないでしょう。異なるシーンマネージャのRaySceneQueriesも異なった方法で実装されています。新しいSceneManagerを使用する場合には、実験して確かめてください。

RaySceneQueryコードに戻りましょう。私たちは全ての結果をループしていません!実際、私たちは最初の結果のワールドジオメトリ(TerrainSceneManagerの場合の)しか見ていません。私たちはTerrainSceneManagerがいつもワールドジオメトリを返すとは確信できないので、これはよくありません。私たちは私たちが探しているものを見つけられるように、結果をループでまわす必要があります。もう一つ私たちがやりたいことは、既に配置されているオブジェクトの"ピックアップ"とドラッグです。今、もしあなたが既に配置されているオブジェクトをクリックしたならば、プログラムはそれを無視し、その後ろにロボットを配置するでしょう。これを修正しましょう。

onLeftPressed関数に行きましょう。私たちが最初にしたいことは、Rayを飛ばして最初に当たったものを取得することです。コレを行うために、私たちはRaySceneQueryを深さによってソートする必要があります。次のコードをonLeftPressed関数から見つけてください:
       // Setup the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);

// Execute query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();
そして次のように変更してください:
       // Setup the ray scene query
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
mRaySceneQuery->setSortByDistance(true);

// Execute query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr;
これで順番に並んだ結果を返すようになったので、クエリ結果のコードを更新する必要があります。私たちはその部分を書き換えたいので、このコードを取り除いてください:
       // Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
Entity *ent;
char name[16];

if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else

mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
私たちは既にスクリーン上に配置されたオブジェクトを選択できるようにしたいです。私たちの戦略は2つの部分に分けられます。最初は、ユーザがオブジェクトをクリックした時に、mCurrentObjectをその親SceneNodeにします。もしユーザがオブジェクトをクリックしなかった場合(かわりに地面をクリックした場合)、私たちがコレまでやってきたように、新しいロボットを配置します。最初の変更は、ifの変わりにforループを使用することです:
       // Get results, create a node/entity on the position
for ( itr = result.begin(); itr != result.end(); itr++ )
{
最初に、私たちは最初に交差したものがMovableObjectかどうかチェックして、もしそうであるならばその親SceneNodeをmCurrentObjectに設定します。TerrainSceneManagerは地面をMovableObjectsとして作成するので、実際にはタイルの一つに交差しているかもしれません。コレを修正するために、わたしはオブジェクトの名前をチェックします。例えば、タイル名を"tile[0][0,2]"といった名前にしておき、タイルに当たったのかどうかを確認します。最後に、breakに注意してください。私たちは最初の物体との交差しか必要ないので、それを見つけた場合にはループをすぐに抜けます。
           if (itr->movable && itr->movable->getName().substr(0, 5) != "tile[")
{
mCurrentObject = itr->movable->getParentSceneNode();
break;
} // if
次に、私たちはmRotobStateに基づいて、ロボットか忍者のEntityを作成します。EntityをSceneNodeに作成した後、breakでループを抜けましょう。
               if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else

mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
break;
} // else if
} // for
信じようが信じまいが、以上で全て行いました!コンパイルして実行してください。私たちは、地面をクリックした場合に正しい種類のオブジェクトを作成でき、オブジェクトをクリックした場合にはバウンディングボックスが見えるでしょう(まだドラッグはできません。次のステップで行います)。一つの疑問点として、私たちは最初の交差を見つけたくて、そして深さによってソートしているのに、なぜif文を使ったのではいけないのでしょうか?主な理由は、ソートした結果で最初に見つけたものがやっかいなタイルである可能性があるからです。ですので、私たちはループをまわして、タイル以外のものを見つけるか、リストの最後までループを回す必要があります。

次に、私たちはRaySceneQueryに関係があるほかの部分も更新する必要があります。frameStartedとonLeftDragged関数の両方で、私たちはTerrainを見つける必要があるだけです。地面はソートリストの最後になるので、ここでは結果をソートする必要がありません(そして私たちはソートしないでしょう)。私たちは、TerrainSceneManagerが最初に地面を返さなくなるようになるまで、結果をループで回したいでしょう。最初に、次のコードをframeStartedから見つけてください:
       // Perform the scene query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();

// Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{
Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition(camPos.x, terrainHeight + 10.0f, camPos.z);
}
そして、以下のように修正してください:
       // Perform the scene query
mRaySceneQuery->setSortByDistance(false);
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr;

// Get the results, set the camera height
for (itr = result.begin(); itr != result.end(); itr++)
{
if (itr->worldFragment)
{
Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition(camPos.x, terrainHeight + 10.0f, camPos.z);
break;
} // if
} // for

これは、一目瞭然です。私たちはソートしないようにして、if文をforループに変換し、私たちが探している位置を見つけたらすぐにbreakしています。私たちは同じようなことをmouseMoved関数でも行います。mouseMoved関数の次の部分を見つけてください。
           mRaySceneQuery->setRay(mouseRay);

RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();

if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
そして次のコードに置き換えてください:
           mRaySceneQuery->setRay(mouseRay);
mRaySceneQuery->setSortByDistance(false);

RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr;

for (itr = result.begin(); itr != result.end(); itr++)
if (itr->worldFragment)
{
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
break;
} // if
コンパイルしてテストしてください。前回と特に変わった結果ではありませんが、正しく動作していて、今後の更新でTerrainSceneManagerがあなたのコードを壊すことはないでしょう。

クエリマスク

何のモードであるかにかかわらず、私たちはオブジェクトを選択したいことに注意してください。私たちのRaySceneQueryは、一番手前にあるロボットか忍者を返します。あなたはすべてのMovableObjectsにマスクを設定することができ、これによってSceneQueriesは結果をフィルタリングすることができます。全てのマスクはバイナリのAND操作を使用することによって行うので、それについて詳しくないのであれば、次に行く前にbrush up on itするべきです。

最初に行うことは、マスク値を作成することです。MouseQueryListenerクラスの最初に戻って、public宣言のあとに次のコードを追加してください:
   enum QueryFlags
{
NINJA_MASK = 1<<0,
ROBOT_MASK = 1<<1
};
これは、enumで2つの値を作成しています。バイナリで0001と0010です。次に、私たちがロボットEntityを作成する時には、"setMask"関数でROBOT_MASKを設定するようにします。忍者Entityを作成する場合も同様に、"setMask"関数でNINJA_MASKを設定します。そして、忍者モードの場合には、RaySceneQueryはNINJA_MASKフラグをもつオブジェクトだけをオブジェクトとみなし、ロボットモードの場合はROBOT_MASKを持つものだけをオブジェクトとみなすようにします。

onLeftPressed関数の次の部分を探してください:
               if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
} // else
私たちは、両方に次のように2行を追加します:
               if (mRobotMode)
{
sprintf(name, "Robot%d", mCount++);
ent = mSceneMgr->createEntity(name, "robot.mesh");
ent->setQueryFlags(ROBOT_MASK);
} // if
else
{
sprintf(name, "Ninja%d", mCount++);
ent = mSceneMgr->createEntity(name, "ninja.mesh");
ent->setQueryFlags(NINJA_MASK);
} // else
私たちは、モードによってクリックやドラッグできるオブジェクトの種類を制限できるようにする必要があります。私たちは、クエリフラグに選択することができるオブジェクトのタイプを設定するだけでできます。具体的には、ロボットモードの場合にはROBOT_MASKをRaySceneQueryのクエリマスクに設定し、忍者モードの時はNINJA_MASKを設定することで達成できます。onLeftPressed関数で、次の部分を探してください:
       mRaySceneQuery->setSortByDistance(true);
次の行をその後に追加してください:
       mRaySceneQuery->setQueryMask(mRobotMode ? ROBOT_MASK : NINJA_MASK);
コンパイルして実行してください。これで、設定したオブジェクトしか選択できなくなったでしょう。全てのレイは正しいオブジェクト以外はすり抜けるようになります。コレでこのコードは終わりです。次のセクションはそれを変更しません。

Query Type Masks

シーンクエリを使用するときに、もう一つ考慮する必要があることがあります。あなたはビルボードやパーティクルシステムをあなたのシーンに追加して、動かしたいと思うかもしれません。そしてあなたはクエリはあなたがクリックしたビルボードを返してくれないことを知るでしょう。この理由は、SceneQueryは、QueryTypeMaskというもう一つのマスクを持っており、コレによって選択できる種類を制限しているからです。デフォルトでは、Entityタイプのオブジェクトしか返しません。

あなたのコードで、もしあなたがBillboardSetsやParticleSystemsを返してもらいたいのであれば、クエリを実行する前にマスクの設定をする必要があります:
mRaySceneQuery->setQueryTypeMask(SceneManager::FX_TYPE_MASK);
これでクエリはBillbordSetsかParticleSystemsだけを返すようになるでしょう。

SceneManagerクラスでは、スタティックメンバとして6つの種類のQueryTypeMaskが定義されています:
WORLD_GEOMETRY_TYPE_MASK //Returns world geometry.
ENTITY_TYPE_MASK //Returns entities.
FX_TYPE_MASK //Returns billboardsets / particle systems.
STATICGEOMETRY_TYPE_MASK //Returns static geometry.
LIGHT_TYPE_MASK //Returns lights.
USER_TYPE_MASK_LIMIT //User type mask limit.
デフォルトのQueryTypeMaskはENTITY_TYPE_MASKです。

さらにMasks

私たちのマスクの例はとても単純なので、少し複雑な例も見ていきましょう。
MobableObjectのマスクの設定
私たちが新しいマスクを作成する時には、1つのマスクが1つの値だけをもつようにしないといけません:
00000001
00000010
00000100
00001000
00010000
00100000
01000000
10000000
そして、私たちはこれをビットシフトすることで簡単に作成することができます:
00000001 = 1<<0
00000010 = 1<<1
00000100 = 1<<2
00001000 = 1<<3
00010000 = 1<<4
00100000 = 1<<5
01000000 = 1<<6
10000000 = 1<<7
最大で1<<31まで設定することができ、32種類のマスクをMovableObjectsに使用することができます。
複数マスクのクエリ
私たちはOR演算子により、複数のマスクをクエリすることができます。ゲームで3つの異なるグループのオブジェクトを持ってみましょう:
enum QueryFlags
{
FRIENDLY_CHARACTERS = 1<<0,
ENEMY_CHARACTERS = 1<<1,
STATIONARY_OBJECTS = 1<<2
};
次に、私たちがフレンドリーなキャラクタだけを問い合わせたい時は、次のようにします:
mRaySceneQuery->setQueryMask(FRIENDLY_CHARACTERS);
もし私たちがエネミーキャラクタとステーショナリーオブジェクトの両方を返したい場合は、次のようにします:
mRaySceneQuery->setQueryMask(ENEMY_CHARACTERS | STATIONARY_OBJECTS);
もしあなたがたくさんの種類のクエリを使うのであれば、次のようなenumを定義したいかもしれません。
OBJECTS_ENEMIES = ENEMY_CHARACTERS | STATIONARY_OBJECTS
この場合は、OBJECTS_ENEMIESを単純に使えばよくなります。
Querying for Everything but a Mask
あなたは一つのオブジェクトを除いて全てを問い合わせるといったこともできます:
mRaySceneQuery->setQueryMask(~FRIENDLY_CHARACTERS);
これでフレンドリーキャラクタ以外の全てを返します。次のようなマスクも使えます:
mRaySceneQuery->setQueryMask(~(FRIENDLY_CHARACTERS | STATIONARY_OBJECTS));
これでフレンドリーキャラクタとステーショナリーオブジェクト以外の全てを返すようになります。
全てのオブジェクトの選択か何も選択しないか
あなたはマスクの面白い特性を見ることができます。クエリマスクQMをSceneQueryに設定した場合、OMというマスクを持つ全てのMovableObjectは、もしQM & OMを計算した場合には少なくとも一つは1になります。従って、SceneQueryに0を設定した場合、MovableObjectsは何も返しません。クエリマスクに~0(0xFFFF...)を設定した場合、0クエリマスクを持つ場合を除いて、すべてのMovableObjectsが返されます。

クエリマスクに0を設定することは、いくつかのシチュエーションでとても有効です。例えば、TerrainSceneManagerがworldFragmentだけを返してほしい時です。このようにします:
mRaySceneQuery->setQueryMask(0);
あなたはSceneManagerのためのRaySceneQueriesのworldFragmentだけを得ることができるでしょう。これは、たくさんのオブジェクトがスクリーン上にあり、あなたがループを回さずに地面との衝突だけを検出したい場合にとても有効です。
練習

簡単な練習

  1. TerrainSceneManagerは~0マスク(全てのクエリが選択できる)を持つタイルをデフォルトでは作成します。私たちはこの問題をタイルの名前"tile[0][0,2]"を見ることで解決しました。まだ実装していませんが、TerrainSceneManagerは複数ページをサポートしており、"tile[0][0,2]"だけではエラーの原因になる恐れがあります。ループでテストする代わりに、TerrainSceneManagerにユニークなマスクを設定することでこの問題を解決してください。(ヒント:TerrainSceneManagerは"Terrain"と呼ばれる全てのタイルを持つSceneNodeを作成します。ループを回し、そのSceneNodeに関連付けられたオブジェクトにあなたが選んだマスクを設定してください。)


中級の練習

  1. 私たちのプログラムは、ロボットと忍者の2つを扱いました。もし私たちがシーンエディタを実装するのであれば、色々なオブジェクトの種類を扱いたいと思うでしょう。あらかじめ定義したリストを使って、どのようなタイプのオブジェクトも配置できるようなコードを生成してください。あなたがエディタで使いたいオブジェクトのリストを作成し(例えばNinjas、Robots、Knots、Ships等)、SceneQuerisで指定したオブジェクトの種類しか選択できないようにしてください。

  2. 私たちは複数の種類のオブジェクトを使用するので、Factory PatternをSceneNodesとEntitiesに使ってみてください。


進んだ練習

  1. 上の練習で、Ogreが知っている(Mediaディレクトリにある)メッシュを全て読み込めるようにして、それらを配置できるようにしてください。 Ogreが配置できるオブジェクトの種類を制限するべきではないことに注意してください。あなたは32個のユニークなクエリマスクしか持てないので、スクリーン上のオブジェクトのための全てのクエリを高速に変更する方法が必要になるかもしれません。

  2. あなたがオブジェクトをクリックした時、オブジェクトはバウンディングボックスの底を"持ち上げられている"ことに気づいたかもしれません。キャラクタの上をクリックして動かしてみてください。彼は即座に他の場所に動かされるでしょう。この問題を修正してください。


今後の勉強のための練習

  1. CTRLを押しながらクリックした複数オブジェクトを選択できるようにしてください。あなたがこれらのオブジェクトを動かした場合、全てのオブジェクトが動きます。

  2. たくさんのシーンエディタプログラムは、オブジェクトをグループ化して一緒に動かすことができます。コレを実装してください。


posted by シンドラー at 05:37| Comment(0) | TrackBack(0) | Ogre Wiki - Intermediate Tutorials | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

※ブログオーナーが承認したコメントのみ表示されます。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/111580640
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック