중급 튜토리얼 3-2
Ogre3D 삽질란/Intermediate Tutorial 3 2008. 12. 11. 17:58
선택된 객체 보여주기
이 튜토리얼에서는 이미 위치시킨 객체를 “픽업”하고 이동시킬 수 있게끔 할 계획입니다. 그리고 어느 객체가 이동시킬 객체인지를 알려줄 수 있게끔 만들려고 합니다. 보통 게임같은 경우는 객체에 특별한 하이라이트를 주지만 이 튜토리얼에서는(그리고 개발단계의 프로그램의 경우처럼) 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";
비록 단순하지만 로봇모드로 동작되게끔 만들었습니다! 이제 mRobotMode변수값으로 생성시킬 메쉬가 로봇인지 닌자인지를 고를 수 있게 해야 합니다. onLeftPressed함수의 다음 코드로 갑니다 :
char name[16];
sprintf(name, "Robot%d", mCount++);
Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
제대로 동작하도록 바로 바꿔줘야 합니다. mRobotMode변수 상태에 따라서 이름이 로봇인지 닌자인지 바뀌게 됩니다.
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";
}
해냈습니다! 데모를 실행시켜보세요. 스페이스바로 생성될 객체를 선택할 수 있게 됩니다.
객체 선택
이제 이 튜토리얼의 핵심내용이 시작됩니다 : RaySceneQuery를 이용해서 화면상의 객체를 선택하기. 코드를 수정하기 전에 RaySceneQueryResultEntry에 대한 좀 더 자세한내용을 미리 설명하고자 합니다. (링크를 따라가서 구조를 한번 보고 진행하세요.)
RaySceneQueryResult에서 리턴되는 결과값은 RaySceneQueryResultEntry 구조체의 반복자입니다. 구조체는 3개의 변수를 내포하고 있습니다. distance변수는 광선으로부터 얼마나 멀리 떨어져 있는지, 나머지 두개 변수들은 non-null로 설정됩니다. movable변수는 만약 만나게 되는 객체가 있다면 MovableObject형식의 값이 저장되고 world fragment(지형같은)에 hit된다면 WorldFragment형식의 값이 저장됩니다.
MovableObject는 기본적으로 SceneNode에 attach될 수 있는 모든 객체들을 포괄합니다(엔티티라던지, 조명, 기타등등). 상속구조를 이 페이지에서 참조하여 어떤 객체가 리턴될 수 있는지를 살펴보세요. 대부분의 응용프로그램에서 RaySceneQuery의 주된 역할은 클릭되었거나 SceneNode에 attach된 MovableObject 객체를 선택하여 조작하기위해 쓰입니다. getName멤버함수로 MovableObject객체의 이름을 얻을 수 있습니다. 객체가 attach된 SceneNode(또는 Node)를 얻으려면 getParentSceneNode(또는 getParentNode)를 호출하면 됩니다. movable변수는 결과값이 MovableObject형식이 아니라면 NULL값을 가지게 됩니다.
WorldFragment는 전혀다른 성질을 가집니다. RaySceneQueryResult 의 worldFragment 멤버가 설정되어 있다면 그 결과값은 SceneManager로 생성된 world geometry의 한 일부분이라는 의미입니다. 리턴되는 world fragment은 SceneManager에 밑바탕을 둡니다. 구현되면서 사용된 WorldFragment 구조체는 fragmentType변수를 가지며 이것은 현재 사용될 수 있는 world fragment의 종류들을 포함하고 있습니다. fragmentType변수는 한가지 종류의 값으로 설정될 수 있습니다(singleIntersection, planes, geometry, 또는 renderOp). 일반적으로 RaySceneQuery가 리턴하는 값은 WFT_SINGLE_INTERSECTION 형식의 WorldFragment값 입니다. singleIntersection값은 단순히 Vector3형식의 교차점입니다. 다른 종류의 world fragment값은 이 튜토리얼의 범주를 벗어납니다.
예제를 한번 보도록 합시다. RaySceneQuery의 결과값을 출력 해보고싶은 경우 입니다. 이 경우 다음과 같은 코드가 필요합니다. (fout객체는 ofstream형식이며 open멤버함수를 통해서 미리 생성되었다는 가정하에 동작됩니다.)
// 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
광선이 교차되는 모든 MovableObject의 이름과 world geometry의 교차점(만약 hit되었다면)을 출력합니다. 참고하실점은 이 코드는 가끔씩 이상하게 동작될 수도 있습니다. 예를들면 TerrainSceneManager를 사용하고 있는 상황에서 광선은 반드시 지형으로 쏘아져야 합니다. 그렇지 않으면 교차점은 hit된 상황과는 다르게 제대로 등록되지 않을 것 입니다. 다른종류의 scene manager에서는 RaySceneQuery를 다른방법으로 사용합니다. 새로운 SceneManager를 사용하게 된다면 반드시 실험해보고 결과값을 확인하세요.
이제 RaySceneQuery코드로 돌아갑시다. 뭔가를 지나친것 같습니다 : 결과값들을 순환하며 살펴보지 않았던 것 입니다! 사실은 world geometry (지금은 TerrainSceneManager경우)의 가장 첫 결과값 만 살펴봤습니다. TerrainSceneManager에 있어서 가장 첫 결과값이 항상 world geometry값이 리턴된다는 보장이 없다는 점에서 좋지 않은 코드입니다. 루프를 돌면서 우리가 원하는 값을 찾도록 해야 합니다. 그리고 “객체 집어내기”와 이미 위치된 객체를 드래그할 수 있는 기능도 필요합니다. 지금시점에서 이미 생성된 객체를 클릭하게 된다면 프로그램은 그 객체를 무시하고 그 위치에 또다른 로봇을 위치시킬 것 입니다. 이제 고쳐봅시다.
onLeftPressed함수로 가서 코드를 살펴봅시다. 클릭을 하게 되면 광선을 따라서 첫 객체를 얻게끔 해야 합니다. 그러기 위해서 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인지 검사합니다. 만약 그렇다면 mCurrentObject값을 그것의 부모 SceneNode값으로 대입합니다. 그런데 여기에 한가지 주의사항이 있습니다. TerrainSceneManager는 자신이 생성한 지형을 위해서 MovableObject를 생성합니다. 그러므로 아마도 지형의 타일 한조각이 교차될 것 입니다. 이 문제점을 고치기 위해서 객체의 이름을 검사해서 지형조각이 선택되지 않게끔 해야 합니다; 타일이름을 예를 들면 “tile[0][0,2]”쯤 됩니다. 마지막으로 break문을 넣습니다. 최초객체에 대해서만 동작하면 됩니다. 그러므로 전체 for루프문에서 빼내야할 필요한 객체는 가능한 일찍 발견되어질 것 입니다.
if (itr->movable && itr->movable->getName().substr(0, 5) != "tile[")
{
mCurrentObject = itr->movable->getParentSceneNode();
break;
} // if
그 다음, 교차점이 WorldFragment일 경우를 검사합니다.
else if (itr->worldFragment)
{
Entity *ent;
char name[16];
이제 mRobotState상태에 따라서 로봇 또는 닌자를 생성해야 합니다. 엔티티를 생성한 이후 SceneNode를 생성하고 for루프를 빠져나옵니다.
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 두 함수들에서 필요한 부분은 오직 지형을 찾는 것 뿐입니다. 결과를 따로 정렬할 필요가 없습니다. 왜냐하면 지형은 항상 마지막에 위치하게 됩니다(그러므로 정렬기능을 off할 것입니다). 그러나 여전히 결과를 순환하며 검사할 필요가 있습니다. 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
설명이 필요없는 코드입니다. 정렬기능을 off 시키고 if 구문을 for 루프로 넣어줬습니다. 그 다음 위치가 찾아지는대로 루프를 빠져나옵니다. 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는 로봇이든 닌자든 가장 앞에 있는것을 리턴합니다. 하지만 그렇게 되서는 안됩니다. 모든 MovableObject에 있어서 객체에 마스크값을 설정하는것이 가능합니다. 그리고 SceneQuery의 결과를 마스크값에 기준하여 필터링하는게 가능합니다. 모든 마스크값은 2진 AND 연산에 수행됩니다. 만약 이게 뭔지 잘 모르신다면 다음내용을 읽기전에 brush up on it파트를 참조하세요.
우선 마스크값을 생성합니다. MouseQueryListener클래스의 가장 첫 부분에 가서 다음의 public값을 추가하세요 :
enum QueryFlags
{
NINJA_MASK = 1<<0,
ROBOT_MASK = 1<<1
};
열거형형식(enum)으로 0001, 0010의 2개의 값이 생성되었습니다. 이제 로봇을 생성시킬때 마다 "setMask"함수를 호출해서 ROBOT_MASK 쿼리flag 값을 설정하게 될 것입니다. 닌자를 생성할때는 NINJA_MASK가 대신 설정될 것입니다. 이제부터 닌자모드의 경우 RaySceneQuery는 오직 NINJA_MASK flag값만 고려합니다. 그리고 로봇모드의 경우는 ROBOT_MASK flag값을 고려하게 될 것입니다.
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
양쪽 모두 1개 라인씩 추가시킵니다 :
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
클릭하고 드래그 할 수 있는 타입이 설정되어 있는 모드타입으로 제한되어야 합니다. 원하는 객체가 선택될 수 있도록 올바른 쿼리 flag값을 설정하는 작업이 필요합니다. 이 기능은 RaySceneQuery의 쿼리마스크 값을 로봇모드에서는 ROBOT_MASK, 닌자모드에서는 NINJA_MASK로 설정하여 구현될 것 입니다. onLeftPressed함수의 다음 코드로 갑시다 :
mRaySceneQuery->setSortByDistance(true);
이 라인 다음에 이 코드를 추가하세요 :
mRaySceneQuery->setQueryMask(mRobotMode ? ROBOT_MASK : NINJA_MASK);
컴파일 후 실행시켜 보세요. 이제 특정 오브젝트타입만 선택가능합니다. 광선은 모든 객체들을 뚫고 지나가며 설정된 객체에만 hit하게 됩니다. 이제 이 코드에 대해서는 더 이상 손댈것이 없습니다. 다음 섹션에서는 더 이상 수정하지 않을 것 입니다.
쿼리타입 마스크
scene query를 사용할때 고려되어야 할 사항이 하나 더 있습니다. 빌보드셋트나 파티클시스템을 장면위에 올려놓고 위치를 이동시키고 싶다고 가정해 봅시다. 빌보드셋을 클릭해도 쿼리값은 리턴되지 않음을 발견하게 될 것입니다. 왜냐하면 SceneQuery는 또다른 마스크인 QueryTypeMask를 가지고 있습니다. 이것은 flag처럼 특정타입 마스크만 선택할 수 있도록 제한시켜 줍니다. 디폴트값으로 쿼리를 수행한다면 오직 엔티티타입의 객체만 리턴합니다.
코드에서 BillboardSets또는 ParticleSystems을 리턴하고 싶다면 쿼리를 수행하기전에 다음코드가 먼저 수행되어야 합니다 :
mRaySceneQuery->setQueryTypeMask(SceneManager::FX_TYPE_MASK);
이제 BillboardSets또는 ParticleSystems의 결과값만 리턴됩니다.
6개 종류의 QueryTypeMask값이 SceneManager클래스 내부에 static member로 선언되어 있습니다 :
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입니다.
마스크의 다양한 사용법
마스크 예제는 무척이나 간단했습니다. 여기서 좀 더 복잡한 예제들을 다뤄봅시다.
MovableObject에 대한 마스크 설정
새로운 마스크를 생성하고 싶다면 반드시 1이 하나밖에 없는 이진값으로 표현되어야 합니다. 저 값들은 올바른 마스크들 입니다 :
00000001
00000010
00000100
00001000
00010000
00100000
01000000
10000000
대충 이렇습니다. 1과 Bit 쉬프팅을 이용해서 간단하게 값을 생성할 수 있습니다. 이렇게 말이죠 :
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 마스크를 제공합니다.
Multiple Mask에 대한 쿼리
비트 OR연산으로 다수의 마스크를 쿼리할 수 있습니다. 예를 들어서 게임에서 3개의 서로다른 그룹의 객체들이 존재한다고 가정합시다 :
enum QueryFlags
{
FRIENDLY_CHARACTERS = 1<<0,
ENEMY_CHARACTERS = 1<<1,
STATIONARY_OBJECTS = 1<<2
};
이제 friendly characters에 대한 쿼리만 수행한다면 다음과 같습니다 :
mRaySceneQuery->setQueryMask(FRIENDLY_CHARACTERS);
만약 2가지종류의 모든 적들, enemy characters와 stationary objects를 쿼리하고 싶다면 이렇게 할 수 있습니다 :
mRaySceneQuery->setQueryMask(ENEMY_CHARACTERS | STATIONARY_OBJECTS);
만약 이러한 쿼리타입이 자주 쓰인다면 다음과 같이 enum값을 정의할 수도 있습니다 :
OBJECTS_ENEMIES = ENEMY_CHARACTERS | STATIONARY_OBJECTS
그리고 간단하게 OBJECTS_ENEMIES로 쿼리 하면 됩니다.
특정 Mask만 제외한 쿼리
Bit 반전을 이용해서 특정마스크를 제외한 모든 쿼리를 수행할 수도 있습니다. 이렇게 말이죠 :
mRaySceneQuery->setQueryMask(~FRIENDLY_CHARACTERS);
friendly characters를 제외한 모든것들을 리턴합니다. 물론 이 방법 대신에 multiple masks를 사용해도 됩니다 :
mRaySceneQuery->setQueryMask(~(FRIENDLY_CHARACTERS | STATIONARY_OBJECTS));
friendly characters와stationary objects를 제외한 다른 모든것을 리턴합니다.
모두 혹은 아무것도 선택하지 않기
특별한 경우 마스크를 이용해서 재미있는 작업을 수행할 수도 있습니다. 기억하셔야 될 점은 SceneQuery로 QM 쿼리마스크를 설정했을시 OM이라는 마스크를 가진 모든 MovableObject들 중에서 QM & OM의 결과값에 하나의 1이라도 포함된다면 매치되는걸로 취급합니다. 그러므로 SceneQuery 마스크를 0으로 쿼리한다면 아무런 MovableObject도 리턴되지 않을 것 입니다. 반면에 ~0 (0xFFFFF...) 값으로 쿼리한다면 마스크값으로 0을 가지지 않는 모든 MovableObject를 리턴하게 될 것입니다.
0 마스크값은 몇몇 상황에서 매우 유용하게 쓰일 수 있습니다. 예를 들면 TerrainSceneManager는 worldFragment를 리턴할때 QueryMask를 사용하지 않습니다. 이렇게 사용하면 말이죠 :
mRaySceneQuery->setQueryMask(0);
해당 SceneManager에 대한 RaySceneQuery에서만 worldFragment를 얻을 수 있을겁니다. 화면상에 객체가 아주 많고 겹치는 모든 객체들을 순환하면서 검사하는데 드는 시간을 낭비하지 않고 오직 지형교차점만 얻고 싶을시 매우 유용하게 쓰일 수 있습니다.