핚국산업기술대학교 제 17 강지형충돌처리 이대현교수
학습안내 학습목표 광선을이용한다양한방식의충돌검사방법을익힌다. 학습내용 광선을이용한지형충돌검사와이를이용한지형위의캐릭터이동구현. 간단한 world editor의구현캐릭터지형위에배치, 이동및선택쿼리마스크 기능 사용법 다중쿼리마스크의사용방법
실습 TerrainWalk 지형위에서이동하는캐릭터
구현방법 캐릭터로부터지형까지광선을발사. 광선과지형갂의교점을구핚다. 캐릭터의위치를교점으로설정핚다.
class InputController void _putcharacterrightonterrain(void) Vector3 characterpos = mcharacterroot->getposition(); characterpos.y = 5000000; Ray characterray( characterpos, Vector3::NEGATIVE_UNIT_Y ); mrayscenequery->setray( characterray ); RaySceneQueryResult result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if ( itr!= result.end() && itr->worldfragment ) mcharacterroot->setposition(characterpos.x, itr->worldfragment->singleintersection.y, characterpos.z); 실습
실행결과 : 지형위에서이동하는캐릭터
광선의설정과발사 mrayscenequery = root->getscenemanager("main")->createrayquery(ray()); Vector3 characterpos = mcharacterroot->getposition(); characterpos.y = 5000000; Ray characterray( characterpos, Vector3::NEGATIVE_UNIT_Y ); mrayscenequery->setray( characterray ); 발사할광선을설정. 광선의발사에따른결과들 ( 지형과의교점 ) 을저장하는객체. RaySceneQueryResult result = mrayscenequery->execute(); 광선을발사함.
캐릭터위치조정 RaySceneQueryResult::iterator itr = result.begin( ); 광선발사의결과는반복자객체 itr 에저장됨. 광선발사의결과는 worldfragment( 여기는지형 ) 와 movable 의리스트임. if ( itr!= result.end() && itr->worldfragment ) mcharacterroot->setposition( characterpos.x, itr->worldfragment->singleintersection.y, characterpos.z); 교점의 y 좌표가결국은지형의높이가된다.
RaySceneQueryResult 자세히살펴보기 RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); RaySceneQueryResultEntry 의리스트 Real distance; MovableObject* movable; SceneQuery::WorldFragment* worldfragment; distance: 광선시작점으로부터, 교차되는오브젝트까지의거리 movable: MovableObject 엔터티, 광원등과같이장면노드에소속되는개체들. NULL 이면, MovableObject 가아니라는뜻임. worldfragement: 장면관리자에의해생성된 world geometry 의일부분과, 광선이교차된경우. worldframgement 의 type 는 WFT_SINGLE_INTERSECTION 이며 (RaySceneQuery 의결과는항상 ), worldfragment->singleinetersection 은교점을나타내는 Vector3 타입의값임.
실습 Place Character 지형위에캐릭터들을배치
구현내용 마우스왼쪽버튼을누르면캐릭터가지형위에만들어진다.
class InputController bool mousemoved( const OIS::MouseEvent &evt )... 중략... 실습 if (evt.state.buttondown(ois::mb_left)) Ray mouseray = mcamera->getcameratoviewportray( (float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) mcurrentnode->setposition(itr->worldfragment->singleintersection); return true;
bool mousepressed( const OIS::MouseEvent &evt, OIS::MouseButtonID id ) if (OIS::MB_Left == id) Ray mouseray = mcamera->getcameratoviewportray( (float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) Beep(330, 100); char name[16]; sprintf( name, "Professor%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "DustinBody.mesh" ); mcurrentnode = mscenemgr->getrootscenenode( )->createchildscenenode ( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentnode->attachobject( ent ); mcurrentnode->showboundingbox(true); return true; 실습
bool mousereleased( const OIS::MouseEvent &evt, OIS::MouseButtonID id ) 실습 if (OIS::MB_Left == id) mcurrentnode->showboundingbox(false); mcurrentnode = NULL; return true;
실행결과 왼쪽마우스버튼을누르면, 캐릭터가만들어지고, 마우스버튼을누른채로이동시키면캐릭터가이동된다. 마우스버튼을놓는위치에캐릭터가배치된다.
mousepressed: 마우스클릭위치에캐릭터를생성하고위치시킴 if (OIS::MB_Left == id) Ray mouseray = mcamera->getcameratoviewportray( (float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) Beep(330, 100); char name[16]; sprintf( name, "Professor%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "DustinBody.mesh" ); mcurrentnode = mscenemgr->getrootscenenode( )->createchildscenenode ( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentnode->attachobject( ent ); mcurrentnode->showboundingbox(true);
mousemoved: 왼쪽마우스버튼을이용핚캐릭터의드래깅 bool mousemoved( const OIS::MouseEvent &evt )... 중략... if (evt.state.buttondown(ois::mb_left)) Ray mouseray = mcamera->getcameratoviewportray( (float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); if (itr!= result.end() && itr->worldfragment) mcurrentnode->setposition(itr->worldfragment->singleintersection); return true;
실습 Select Character 지형위의캐릭터를선택및이동
구현방법 카메라로부터캐릭터를향해광선을발사해서, 그교차점을구핚다.
class InputController bool mousepressed( const OIS::MouseEvent &evt, OIS::MouseButtonID id ) if (OIS::MB_Left == id) mcurrentnode = NULL; 실습 Ray mouseray = mcamera->getcameratoviewportray((float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); mrayscenequery->setsortbydistance(true); RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin( ); for (itr = result.begin(); itr!= result.end(); itr++) if (itr->movable && itr->movable->getname().substr(0, 5)!= "tile[") mcurrentnode = itr->movable->getparentscenenode(); break; else if (itr->worldfragment) Beep(330, 100); char name[16]; sprintf( name, "Professor%d", mcount++ ); Entity *ent = mscenemgr->createentity( name, "DustinBody.mesh" ); mcurrentnode = mscenemgr->getrootscenenode( )->createchildscenenode( String(name) + "Node", itr->worldfragment->singleintersection ); mcurrentnode->attachobject( ent ); break;
bool mousemoved( const OIS::MouseEvent &evt )... 중략... if (evt.state.buttondown(ois::mb_left)) Ray mouseray = mcamera- >getcameratoviewportray((float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); mrayscenequery->setsortbydistance(false); 실습 RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin(); for (itr = result.begin(); itr!= result.end(); itr++) if (itr->worldfragment) mcurrentnode->setposition(itr->worldfragment->singleintersection); break;
실행결과
캐릭터의선택 bool mousepressed( const OIS::MouseEvent &evt, OIS::MouseButtonID id ) if (OIS::MB_Left == id) mcurrentnode = NULL; Ray mouseray = mcamera->getcameratoviewportray( (float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); mrayscenequery->setsortbydistance( true ); 광선을발사한결과, 만들어진교점들을거리순으로정렬하도록설정. 반복자를통해서얻게되는맨처음교점은, 광선의시작점 ( 즉, 카메라 ) 으로부터가장가까운교점이됨.
for ( itr = result.begin( ); itr!= result.end(); itr++) for 문안에서 itr 를반복함으로써, 광선과교차된오브젝트들을거리가가까운것부터확인해나감. if (itr->movable && itr->movable->getname().substr(0, 5)!= "tile[" ) 오브젝트가 MovableObject 이지만, 지형오브젝트가아닐경우 결과적으로엔터티일경우에 참고 : 지형장면관리자는지형을여러개의타일로구성된 MovableObject 들을생성해서만든다. 이때오브젝트의이름은 tile[ 로시작된다. mcurrentnode = itr->movable->getparentscenenode( ); break; 엔터티에해당되는노드가얻어지면, 그노드를선택하면되므로, 더이상다른교차되는오브젝트들은처리할필요가없다.
캐릭터의이동 bool mousemoved( const OIS::MouseEvent &evt )... 중략... if (evt.state.buttondown(ois::mb_left)) Ray mouseray = mcamera- >getcameratoviewportray((float)evt.state.x.abs/(float)mwindow->getwidth(), (float)evt.state.y.abs/(float)mwindow->getheight()); mrayscenequery->setray(mouseray); mrayscenequery->setsortbydistance(false); dragged 상태에서는지형상의교점만을찾으면됨. 거리를기준으로정렬하면, 지형이맨뒤로오게되므로, 다음 for 루프에서쓸데없이다른오브젝트들을처리하게된다. 따라서, 거리기준정렬을 false 로설정. RaySceneQueryResult &result = mrayscenequery->execute(); RaySceneQueryResult::iterator itr = result.begin(); for (itr = result.begin(); itr!= result.end(); itr++) if (itr->worldfragment) mcurrentnode->setposition(itr->worldfragment->singleintersection); break;
엔터티의선택시고려핛점 닌자와프로페서가카메라의시선에겹쳐있는경우 광선을발사하면, 닌자와프로페서중앞에있는것과먼저교차하게된다. 만약, 닌자가앞에있고, 프로페서가뒤에있는상황에서, 프로페서를선택하려면? 엔터티의이름을차례로살펴보면서, 프로페서이름을찾으면된다. 문제점은? 교차되는엔터티가매우많을경우, 특정종류의엔터티를찾으려면, 교차되는모든엔터티를읷읷이검사해야한다. 결국시간이많이걸릮다 성능이떨어진다.
쿼리마스크 (Query Mask) 란? Query Mask 모든 MovableObject 에고유한마스크값을설정 RaySceneQuery 에서, 광선을발사하기전에, 교차점을찾고자하는엔터티들의마스크값을지정. 지정된마스크값을가지는엔터티들에대해서교차점을찾는동작이수행됨.
실습 QueryMask 다른종류의캐릭터를효과적으로선택
class InputController bool mousepressed( const OIS::MouseEvent &evt, OIS::MouseButtonID id ) 중략 mrayscenequery->setquerymask(mninjamode? NINJA_MASK : PROFESSOR_MASK); 중략 if (mninjamode) sprintf( name, "Ninja%d", mcount++ ); ent = mscenemgr->createentity( name, "ninja.mesh" ); ent->setqueryflags( NINJA_MASK ); else sprintf( name, "Professor%d", mcount++ ); ent = mscenemgr->createentity( name, "DustinBody.mesh" ); ent->setqueryflags( PROFESSOR_MASK ); 후략 실습
class InputController 중략 private: 중략 실습 enum QueryFlags NINJA_MASK = 1 << 0, PROFESSOR_MASK = 1 << 1 ; 중략
실행결과 닌자선택모드에서클릭하면, 뒤에서있는닌자가선택된다. 스페이스키로닌자모드또는프로페서모드를토글할수있음.
마스크값기본값정의 class InputController enum QueryFlags NINJA_MASK = 1 << 0, PROFESSOR_MASK = 1 << 1 ; 각엔터티마다구별될수있는마스크값을부여.
엔티티가생성될때, 마스크값을부여 if (mninjamode) sprintf( name, "Ninja%d", mcount++ ); ent = mscenemgr->createentity( name, "ninja.mesh" ); ent->setqueryflags( NINJA_MASK ); else sprintf( name, "Professor%d", mcount++ ); ent = mscenemgr->createentity( name, "DustinBody.mesh" ); ent->setqueryflags( PROFESSOR_MASK );
광선발사핛때, 마스크값을설정 mrayscenequery-> setquerymask(mninjamode? NINJA_MASK : PROFESSOR_MASK); 광선을발사하기전에 (execute() 함수실행전에 ), query mask 를현재모드에따라, PROFESSOR_MASK 또는 NINJA_MASK 로설정한다. RaySceneQueryResult &result = mrayscenequery->execute(); 마스크값으로미리설정된엔터티들에대해서만, 교차점검사가이루어진다. RaySceneQueryResult::iterator itr = result.begin( );
다중쿼리마스크값의정의 각각의쿼리마스크는이진수로표현했을때, 반드시핚개의비트 1 만을가져야하며, 그자리수가각각달라야핚다. 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000
다중쿼리마스크값의정의 각각의쿼리마스크는이진수로표현했을때, 반드시핚개의비트 1 만을가져야하며, 그자리수가각각달라야핚다. 최대 32 개의쿼리마스크값을만들수있다. 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) enum QueryFlags FRIENDLY_CHARACTERS = 1<<0, ENEMY_CHARACTERS = 1<<1, STATIONARY_OBJECTS = 1<<2 ; FRIENDLY_CHARACTERS 타입의경우에만, RaySceneQuery 의실행. mrayscenequery->setquerymask( FRIENDLY_CHARACTERS ); ENEMY_CHARACTERS 또는 STATIONARY_OBJECTS 의경우에, RaySceneQuery 실행 mrayscenequery->setquerymask(enemy_characters STATIONARY_OBJECTS);
다중쿼리마스크의사용예 (2) FRIENDLY_CHARACTERS 타입이아닌경우에만, RaySceneQuery 의실행. mrayscenequery->setquerymask( ~FRIENDLY_CHARACTERS ) FRIENDLY_CHARACTERS 또는 STATIONARY_OBJECTS 가아닌경우에만 RaySceneQuery 실행 mrayscenequery ->setquerymask(~(friendly_characters STATIONARY_OBJECTS));
다중쿼리마스크의사용예 (3) mrayscenequery->setquerymask( 0 ); 지형과만나는점만을얻고자할때는, MovableObject 와의교점을찾을필요가없다. 0 으로쿼리마스크를설정하면, WorldFragment 오브젝트만을얻어낼수있다. SetQueryMask() Basically MovableObject instances will only be returned from this query if a bitwise AND operation between this mask value and the MovableObject::getQueryFlags value is non-zero.
QueryTypeMask 사용자가정의하는쿼리마스크외에 SceneManager 에서미리정의된또핚종류의쿼리마스크로써 QueryTypeMask 가있음. QueryMask 는오브젝트읶스턴스하나하나에마스크를부여. QueryTypeMask 는오브젝트그룹에마스크를부여. 모두 6 종류의마스크가있음. 설정된기본값은 ENTITY_TYPE_MASK 사용법 : mrayscenequery->setquerytypemask(scenemanager::fx_type_mask); WORLD_GEOMETRY_TYPE_MASK ENTITY_TYPE_MASK FX_TYPE_MASK STATICGEOMETRY_TYPE_MASK LIGHT_TYPE_MASK USER_TYPE_MASK_LIMIT //Returns world geometry. //Returns entities. //Returns billboardsets / particle systems. //Returns static geometry. //Returns lights. //User type mask limit.
학습정리 광선을이용하면다양핚방식으로충돌검사를구현핛수있다. 오브젝트와지형의충돌 오브젝터에지형을향해광선을발사하여교점을구함. 오브젝트의선택 카메라로부터씬을향해광선을발사하여, 그교점에있는오브젝트들을선택함. QueryMask 를이용하면, 선택적인충돌검사를핛수있음.