2010. 7. 20. 23:01

'형식 캐스팅' : ...로의 변환이 있지만 액세스할 수 없습니다.


CTest* a;
CParent* p = (CParent*)a;
등과 같이 부모클래스로 캐스팅할때..

아래처럼 실수로 쓰지 상속관계를 정의하지 않은 경우에 private으로 자동설정되어 접근할 수 없어 발생하는 에러다. 간단히 상속관계를 public으로 바꿔주면 해결된다.

CTest : CParent
{
};
를..

CTest : public CParent
{
};
로 바꿔주면 된다.


애초에 private 상속은 두가지 규칙을 가진다. 아래는 Effective C++에 나온 부분을 발췌한것.
첫번째로 부모클래스로의 변환불가
두번째로 부모클래스의 멤버들이 자식클래스의 private 멤버가 된다.(출처 Effective C++)
기타..

따라서 저런 에러가 발생하는 것이다.
2010. 7. 16. 05:06

모델이 깨지는경우



에러가 났었습니다... 여러가지 이유가 있었겠지만 그림자가 생긴걸 봐서는.. 렌더링이 잘못되었겠군요.
저런 경우 거의 대부분이 렌더링 문제입니다.. 그리고 실제 깨어진모델과 그의 그림자가 동시에 나온걸 봐서는.. 그림자를 그린 부분만 에러가 났다던지 하는 국소적인 문제는 아닌것 같았습니다.

생각해본 원인은 버텍스버퍼가 잘못되었다던지.. 인덱스 버퍼가 잘못되었다던지.. 버텍스 셰이더에서 버텍스를 잘못 건드려줬다던지.. 엉뚱한 곳에 픽셀셰이더로 칠했다던지...하지만 픽셀셰이더는 모델이 렌더링되는 부분에만 생기는 것이라.. 더욱이 그림자까지 생겼으므로 픽셀셰이더는 아니었습니다.

그럼 버텍스버퍼, 인덱스버퍼, 버텍스 셰이더로 좁혀지는데, 버텍스 셰이더의 경우 현재 스키닝처리가 확률이 컸었는데.. 만약 스키닝이 잘못되었다면 버텍스가 연결되어있어 결과적으로 깨진 모델의 일부분이 스키닝 모델과 계속 붙어다니는 현상을 보게 될텐데.. 저 위 모델은 스키닝 모델인데도 불구하고 전혀 깨지지 않았으니 버텍스 셰이더 문제도 아니었습니다.

그렇다면 버텍스 버퍼문제 또는 인덱스 버퍼의 문제였는데.. 아래를 보면 깨진 모델의 어떤 정점이 0,0,0으로 향하는 것을 볼 수 있습니다. 이것은 기본값 내지, 쓰레기값을 참조하고 있는 경우라고 생각되었습니다. 그리고 현재 뛰어다니는 모델들과는 전혀 상관없는, 지형쪽의 버텍스 버퍼나 인덱스 버퍼가 잘못된 걸로 추측되었습니다.

그러니까.. 지형의 버텍스 버퍼와 인덱스 버퍼의.. 버텍스 개수, 인덱스 삼각형 개수 등이 잘못되어 각 개수의 한계치를 넘게 가져온다던지 하는 것이겠죠.(메모리 침범이 되겠네요) 실제로 제가 설계한 인덱스 버퍼에서는 생성할 때, 삼각형의 개수를 저장시키고, Draw를 할 때 저장했던 삼각형 개수를 리턴해서 primitivecount를 세팅하곤 했었습니다.

먼저 버텍스버퍼.. 지형의 버텍스는 일반적인 높이맵을 사용한 것으로.. 상당히 심플합니다. 따라서 패쓰..

남은건 인덱스 버퍼인데, SLOD기법으로 해서 인덱스 버퍼가 좀 많았죠. 인덱스 버퍼를 살펴보다가 인덱스 버퍼를 세팅하는 부분에서 파라메터를 잘못 설정해주는 실수를 범했었습니다. 삼각형의 개수를 잘못 지정했고, 그걸 렌더링할때 파라메터로 넘겨줬던거죠. 근데.. 지형은 참 오래전에 완성했었는데.. 이제서야 이렇게 나오니까 솔직히 지형쪽에 문제가 있으리라곤 상상도 못했었습니다. 뭐죠? 오래하다보니 점점 심볼들이 늘어나서 평소에는 침범해도 별 해가 없다가(또는 0,0,0의 인덱스만 가져오다가) 프로그램 심볼들이 늘어나서 침범하고, 이상한 인덱스값으로 세팅하는 건가요? 흐음. 아무튼 인덱스 버퍼의 문제였었습니다.

그예로 지형의 버텍스만 참조하는 것을 볼 수 있습니다^ㅡ^ 참고로 뛰어다니는 모델들한텐 붙어있지 않았어요.

'작업일지' 카테고리의 다른 글

캐릭터 충돌처리  (1) 2010.07.07
오리엔테이션 생성중 실수.  (0) 2010.07.05
그림자 처리..  (0) 2010.06.26
Perspective Shadow Map..  (0) 2010.06.22
2010. 7. 8. 22:49

typedef를 활용한 private범위의 struct나 class가져오기


뭐.. private에 선언한 struct나 class를 외부에서 쓰고 싶은 경우엔.. 그냥 public에 선언해버리면 되지만.. 무조건 public보다 외부에 알리는데는 이런 심볼이름을 해야겠다.. 라는 정의가 필요할 수 있다. 집안에선 자랑스런 남편이고 아들일테고 밖에서는 또 역할이 다르지 않은가? 여튼 고민하시고 혹시나 필요하신 분을 위해...
-------- A.h --------
#include "B.h"
class A
{
    B::BST* st;
};

-------- B.h --------
class B
{
public:
 typedef struct ST BST;

private:
 struct ST
 {
 };
};

2010. 7. 8. 22:48

컴파일러에 암시 또는 힌트


컴파일러에게 힌트를 주는 행위
특징 : 중복되어도 에러가 나지 않는다. 현재 시점에서 그것이 존재한다고 가정하고 컴파일한다. 나중에 링크할때 결합하게 된다.

extern변수의 선언(정의 아님)
extern int g;

앞으로 나올 클래스다. 라고 힌트
class Test;

구조체도 마찬가지
struct Test;

단, 클래스(구조체)내부에 들어있는 클래스(구조체)는 위와 같은 방법으로 불가능하다. 현 시점으론 내포된 클래스(구조체)인지 모르기 때문이다.
class Test::ST; X

함수의 원형
void abc();

인라인함수는 힌트는 아니지만 중복된다고 해서 에러는 나지 않는다. 단.. 중복되었을 때 함수가 수정된 경우 영향이 있는 헤더의 인라인 함수를 추가하니 주의

같은 형식의 typedef는 힌트는 아니다. 다만 중복된다고 해서 에러는 나지 않는다. 단, 다른 심벌로 바꾸면 에러가 발생함(typedef int INT; 했다가 typedef int Int;등으로 바꾸면 에러)
typedef int INT;
2010. 7. 7. 19:43

파일간 #include 설계에 대한 생각


프로그래밍을 반복할 때마다 어떻게해야 include를 효율적으로 나타낼수 있을까 수없이 고민을 해왔다. 먼저 필요한 헤더만 포함하는 것은 가독성에 상당히 좋으므로 참으로 현명한 방법이라 생각한다. 하지만 이는 때때로 치명적이다. c/c++에서는 include를 중복으로 해버리면 에러가 나버리고, 교차포함한 경우 짜증지수를 완전 올려주는 원인이 되기도 한다. 그 외로 include가 필요하지 않은 곳에 하거나 include 설계를 잘못한 경우 엄청난 컴파일시간에 직면할 수도 있기 때문이다. 그렇다면 최고의 방법은 무엇일까.. 아직도 잘 모르겠다.

1. 아무것도 포함하지 않는 헤더는 필요하다면 일단 무조건 포함시킨다.
'아무것도' 라는말이 좀 걸리는데 사실 c/c++ 표준라이브러리는 포함되어도 아무것도 포함되지 않은 헤더로 간주한다. c/c++ 표준 라이브러리에서 우리의 라이브러리를 포함할 이유가 없기 때문에 교차포함의 두려움이 없다. 따라서 이건 명백하게 포함시킬 수 있는 부분이다. 헤더가 아무것도 포함하지 않았으므로 교차포함의 두려움은 없다. 흔히 이런 헤더는 어떠한 자료구조를 모아놓은 경우가 많다. 버텍스를 정의하는 구조체.. 어떤 기능을 정의하는 구조체. 만약 그 헤더에 우리 라이브러리중 하나의 헤더를 포함하고 있다면 조심할 필요가 있다.

2. 헤더에는 짧은 시간에 자주 실행되는 인라인 함수만 포함시킨다.
이것은 당연한 얘기로.. 짧은 시간에 자주 실행되는 함수라면 최적화를 위해 인라인 함수(물론 아주 짧은 함수의 경우)로 만들어야 한다. 그것을 제외하고는 소스파일에 포함시키는 방법이다. 아래는 상속을 받은 경우 포함할 필요가 없다는 것을 보여준다. 어차피 컴파일은 소스파일로 하는 것이니까. 단, 포함 순서가 중요하다
----------- Parent.h --------------
class CParent
{
};

----------- Child.h --------------
class CChild : public CParent
{
}

----------- Child.cpp --------------
#include "Parent.h"
#include "Child.h"
...


3. 부득이 교차포함해야 하는 경우는 부모급 헤더에서만 포함시킨다.
관리하기 아주 쉬운 형태로 부모급헤더에만 포함시키고, 자식금 헤더에서는 class CTest; 라는 식으로 암시만 주고, 소스파일에서 부모급헤더를 포함시키는 방법이다. 프로그래밍하다보면 자식급의 부류가 상당히 많아질 수 있는데.. 그럴때 관리하기 아주 편한 형태라 생각한다. 하지만 이런 구조의 경우 포함되는 부모급클래스를 인라인함수로 호출할 수 없다. 그러나 자식급 클래스는 대부분 부모급 클래스와 생명줄이 연결되어 있긴 하지만 부모클래스에 어떤 처리를 부탁하는 일이 없기 때문에 인라인 함수를 만들라고 하더라도 부모급 클래스의 레퍼런스만 리턴해주는 경우가 대부분이기 때문에 문제가 되는 경우는 드물다. 하지만 부모급 헤더에서는 인라인 함수가 자주 나올 확률이 높다. 그렇기 때문에 부모급 헤더에만 포함시키는 것이 좋다.
이 경우는 장점과 단점을 모두 지니는데, 그렇게 하다보면 부모급 클래스에선 다량의 헤더파일이 추가될 것이고 그것을 필요로 하는 모든 소스파일은 모두 다시 컴파일 될것이다.(물론 헤더가 수정되었을 때 이야기다) 하지만 포함시킬때는 부모클래스헤더만 포함시키면 그 예하에 포함되어있는 모든 헤더가 포함된셈이므로 포함할 필요가 없긴하다. 하지만 이 경우 가독성이 떨어지는 문제가 생기기도 한다. 아래를 보자.
----------- Mgr.h --------------
#include "A.h"
#include "B.h"
#include "C.h"
class CMgr
{
   A* a;
   B* b;
   C* c;
};

----------- A.h --------------
class CMgr;
class CB;
class CA
{
   CMgr* mgr;
   CB* b;
};

----------- A.cpp --------------
#include "A.h"
#include "Mgr.h"

CA::CA()
{
   mgr ..
   b ..
}

단순히 저렇게 처리할 수도 있다는 얘기다. 심지어 A.cpp의 A.h를 없애도 상관은 없다. (지금 상태로는) 가독성이 떨어진다고 말했었는데 A.cpp에 B.h가 include되어있다면 A.cpp에서 B.h를 쓰고있구나.. 라는 정보를 얻을 수 있다. 하지만 저 상태로는 B.h를 쓰고 있구나 하는 정보를 당장엔 알 수 없다. 또한 여기서 B.h헤더를 고쳐주게 되면 Mgr.cpp가 재컴파일되고 A.cpp도 재컴파일된다. 그 이유는 B.h가 Mgr.h에 포함되어있고 Mgr.g는 A.cpp에 포함되어 있기 때문이다. 이것은 프로젝트가 거대하다면 단 한글자만 고쳐도 엄청난 컴파일시간이 도사릴 수 있다는 것을 알려준다. 따라서 Mgr.h만 포함하면 모두 다 될거란 생각은 버려야한다. 컴파일 시간이 두렵다면.. 하지만 불행중 다행으로.. B.h가 고쳐진 경우가 아닌, B.cpp가 고쳐진경우는 링크만 다시 되는 형태로 된다. 왜냐하면 헤더는 변하지 않았으니까. 따라서 인라인함수가 많거나 템플릿은 저렇게 포함될 때 고민좀 해봐야한다. 또한 #pragma once 등으로 A.cpp에 Mgr.h와 B.h를 둘다 포함시킬 수 있다. 그러면 가독성은 일단 해결이다.


4. 1, 2번만을 활용하고 소스파일엔 필요한 것만 포함시킨다.
어떻게 보면 3번은 컴파일시간을 대량으로 늘리는 계기가 될수도 있다고 생각한다. 따라서 3번도 2번으로 최적화 시킨 후, 소스파일에는 일일이 필요한 것만 포함시킨다. 이것은 간혹 너무도 귀찮지만(실제로 너무 귀찮다.), 무슨 헤더가 포함되어있는지 알기 쉽고 컴파일시간을 최소화시켜주기 때문이다.

5. 비슷한 부류는 하나의 헤더로 묶은 후 그 헤더를 포함시킨다.
이것도 당연한 얘기다. 일단 여러 헤더를 하나로 묶으면 더 간결해지고 알아보기 쉬운 형태로 바뀌게 된다. 특히 이런 것들은 미리 컴파일된 헤더(또는 라이브러리)로 만들어놓고 묶어서 헤더를 포함시키는 경우가 많다. 예를 들어 특정 외부 라이브러리를 대표하는 헤더파일들이 그 예라고 볼 수 있겠다. (ex) ogre.h, d3d9.h)
2010. 7. 7. 01:34

캐릭터 충돌처리


캐릭터들간 뚫고 들어가지 않게끔 하는 충돌처리..
이것도 날 너무나도 재밌게 해주는구나...

처음.
각 캐릭터들을 OBB & OBB로 처리하다.

회전할때 부딪히는 경우가 발생. 회전할때는 자유롭게 해주고 싶었음.. 따라서 회전할때 충돌처리를 해주지 않았더니.. OBB특성상 끼는 경우가 발생. 그 결과 회전을 하고 난 후 캐릭터가 안움직였다.(충돌상태가 아닌데도) 당연하지. 끼었으니까
소스는 없다. 아쉽게도.

두번째.
각 캐릭터들을 Sphere & Sphere로 처리하다.

처음의 문제를 알고 다방면의 길이가 똑같은 충돌체가 필요하다고 생각되었다.  그때 생각난 것이 Sphere와 Cylinder. 우선 Sphere로 구현하기로 했다. 그 결과 회전을 하고 난 후 캐릭터가 안움직였던 점은 고쳐졌다. 미끄러짐 벡터를 활용해서 옆으로 비껴나가게끔도 만들어놓고.
하지만 여전히 문제점은 발생했다. 비교적 빠르게 이동할때 몬스터들의 Sphere가 너무 작다면 그걸 뚫고 지나가는 상황이 발생한 것. 그리고 말도 안되는 공간으로 미끄러지는 것도 발견. 몬스터가 가리고 있지만, 가리는 공간의 굵기가 현저히 작아서 마치 뚫고가는 것 같은 데, 몬스터의 Sphere에 비해 속도가 빨랐던 모양이다. 그리고 여러 사이클을 처리하지 않아 겹치는 현상도 생겼다. 마지막으로 나중에 눈치채게 된거지만, 정적인 Sphere로 처리하면 이후 움직임처리에서 어긋나버리는 경우가 있다. 예를 들어 대상은 도망가는 입장인데 따라오는 오브젝트가 속도가 빨라 부딪혀 멈춰진다.

 if( deltaPosition.x == 0.0f &&
   deltaPosition.y == 0.0f &&
   deltaPosition.z == 0.0f ) return;

 BOOL isAffectCollision = FALSE;

 Sphere src((*m_pSceneObject->GetPosition()+deltaPosition)+m_collisionSphere.center,
 m_collisionSphere.radius );
std::vector<CCharacter*>* pCharList = m_pObjectManager->GetAllCharacterList();
for( std::vector<CCharacter*>::iterator it = pCharList->begin() ;
 it != pCharList->end() ;
 it++ )
{
 if( *it == this ) continue;
 CCharacter* pDestCharacter = *it;
 // 경계구를 가져온 후, 미리 이동시켜준다.
 Sphere dest( pDestCharacter->GetPosition()+pDestCharacter->GetCollisionSphere().center,
  pDestCharacter->GetCollisionSphere().radius );
 D3DXVECTOR3 contactNormal; // 충돌법선
 FLOAT penetration;   // 관통값
 if( CCollisionDetector::IsIntersectSphereAndSphere( src, dest, &contactNormal, &penetration ) )
 {
  // 관통했다면 그만큼 복구시켜준다.
  m_pSceneObject->AddPosition( deltaPosition+(contactNormal*penetration) );
  // 미끄러짐 벡터로 만들어준다.
  D3DXVECTOR3 slidingPos = deltaPosition-D3DXVec3Dot( &deltaPosition, &contactNormal )*contactNormal;
  //m_pSceneObject->AddPosition( slidingPos );
  // 캡슐을 정의한다.(관통이 복구된 부분부터 미끄러지는 부분까지)
  // 미끄러짐벡터로 이동된 부분까지를 캡슐로 정의한다.
  // 미끄러졌을때 어떤 오브젝트와 부딪힌경우 움직임을 취소시키기 위함이다.
  // 2로 계속 나누어줘서 보간할수도 있지만 그것은 생략했다.
  saengine::Capsule capsule( *m_pSceneObject->GetPosition(),
   *m_pSceneObject->GetPosition()+slidingPos,
   m_collisionSphere.radius );
  std::vector<CCharacter*>::iterator it2;
  for( it2 = pCharList->begin() ;
   it2 != pCharList->end() ;
   it2++ )
  {
   if( *it2 == this ) continue;
   CCharacter* pDestCharacter2 = *it2;
   // 경계구를 가져온 후, 미리 이동시켜준다.
   Sphere dest2( pDestCharacter2->GetPosition()+pDestCharacter2->GetCollisionSphere().center,
    pDestCharacter2->GetCollisionSphere().radius );
   if( CCollisionDetector::IsIntersectSphereAndCapsule( dest2, capsule ) )
   {
    break;
   }
  }
  // 캡슐을 체크했는데 아무도 부딪히지 않았다면 미끄러짐 벡터로 이동시켜준다.
  if( it2 != pCharList->end() )
   m_pSceneObject->AddPosition( slidingPos );

  isAffectCollision = TRUE;
  
  if( m_pBrain ) m_pBrain->HandleMessage( MSG_COLLISION_WITH_CHARACTER, pDestCharacter, 4 );
 }
}
// 충돌하지 않았다면 그냥 이동시켜주면 된다.
if( !isAffectCollision )
{
 m_vecPrevPosition = *m_pSceneObject->GetPosition();
 m_pSceneObject->AddPosition( deltaPosition );
}
 


세번째.
각 캐릭터들을 Capsule & Sphere로 처리하다.

이동 행위자 캐릭터의 처음위치, 이동 완료된 위치, 반지름을 가지고 캡슐을 생성한다. 그리고 그 캡슐과 몬스터의 Sphere로 처리했다. 마치 지나간 궤적같은 느낌의 캡슐. 첨에 이 방식을 기획했을 때는 완벽할줄 알았다. 하지만 그것도 잠시, 충돌후 미끄러지는 방향을 계산해야했는데 충돌하는 케이스가 너무 많았다. 미끄러지는 방향을 구하기 위해 여러가지 계산을 해야하는데, 결과가 부동소수점의 오차때문에 어쩔때는 0.001... 어쩔때는 0.002... 이런식으로 오차가 남아서 툭하면 안움직였었다.(끼어버려서) 밑에 진한 글씨로 되어있는 부분이 충돌후 방향을 정하는 부분이다. 여러번의 곱셈으로 인한 오차가 미세하게 존재했다.
if( deltaPosition.x == 0.0f &&
   deltaPosition.y == 0.0f &&
   deltaPosition.z == 0.0f ) return;
 BOOL isAffectCollision = FALSE;
 // 충돌된 캐릭터는 이곳에 적재된다.
 std::vector<IntersectedCharData> alignedIntersectedCharList;
 // 처음위치와 이동한후의 위치를 이어서 캡슐로 만든다.
 D3DXVECTOR3 desiredPosition = *m_pSceneObject->GetPosition()+deltaPosition;
 saengine::Capsule srcBound( *m_pSceneObject->GetPosition(), desiredPosition, m_collisionSphere.radius );
 std::vector<CCharacter*>* pCharList = m_pObjectManager->GetAllCharacterList();
 std::vector<CCharacter*>::iterator it = pCharList->begin();
 while( it != pCharList->end() )
 {
  CCharacter* pDestChar = *it++;
  if( pDestChar == this ) continue;
  // 충돌을 원하는 구를 지정한다.
  Sphere destBound( pDestChar->GetPosition()+pDestChar->GetCollisionSphere().center,
   pDestChar->GetCollisionSphere().radius );
  D3DXVECTOR3 contactPoint;
  D3DXVECTOR3 contactNormal; // 충돌법선
  FLOAT penetration;   // 관통값
  if( CCollisionDetector::TestIntersectSphereAndCapsule( destBound, srcBound, &contactPoint, &contactNormal, &penetration ) )
  {
   if( penetration > 0.2f )
   {
    // TestIntersectSphereAndCapsule은 캡슐에서부터 구로 충돌법선이 생성된다.
    // 하지만 이 경우는 구에서부터 캡슐에서의 충돌법선이 필요하기 때문에 반전시켜준다.
    // 이 경우는 제 소스가 이렇기 때문입니다.
    contactNormal = -contactNormal;
    // 충돌점과 거리를 측정한다.(가까운곳으로 정렬하여 먼저 처리하기 위함)
    // 충돌점은 캡슐내부의 세그먼트에 정의된다.
    alignedIntersectedCharList.push_back(
     IntersectedCharData( pDestChar,
          contactPoint,
          contactNormal,
          penetration,
          D3DXVec3Length( &( contactPoint-destBound.center ) ) ) );
    }
  }
 }

 // 비어있다면 그냥 종료
 if( alignedIntersectedCharList.empty() )
 {
  m_vecPrevPosition = *m_pSceneObject->GetPosition();
  m_pSceneObject->AddPosition( deltaPosition );
  return;
 }

 // 충돌 캐릭터들을 정렬한다. 정렬한후 제일처음의 오브젝트로 처리했다.
 sort( alignedIntersectedCharList.begin(), alignedIntersectedCharList.end(), AlignIntersectedCharList );

 const IntersectedCharData& interData = *alignedIntersectedCharList.begin();
 if( alignedIntersectedCharList.begin()->penetration > 0 )
 {
  if( interData.contactPoint == srcBound.b )
  {
   D3DXVECTOR3 dir;
   D3DXVec3Normalize( &dir, &deltaPosition );
   desiredPosition = interData.contactPoint+
    (-dir*interData.penetration);
   m_pSceneObject->SetPosition( desiredPosition );
  }
  else if( interData.contactPoint == srcBound.a )
  {
   D3DXVECTOR3 dir;
   D3DXVec3Normalize( &dir, &deltaPosition );
   desiredPosition = interData.contactPoint+
    (dir*interData.penetration);
   m_pSceneObject->SetPosition( desiredPosition );
  }
  else
  {
   D3DXVECTOR3 dir;
   D3DXVec3Normalize( &dir, &deltaPosition );
   desiredPosition = interData.contactPoint+
    (-dir*(interData.pCharacter->GetCollisionSphere().radius+
    srcBound.radius) );
   m_pSceneObject->SetPosition( desiredPosition );
  } 
 }
 // 미끄러짐 벡터로 이동시킨다. (V-(V*N)N)을 속도가 아닌 포지션으로 응용
 D3DXVECTOR3 slidingPos =
  deltaPosition-
  D3DXVec3Dot( &deltaPosition,
     &alignedIntersectedCharList.begin()->contactNormal ) *
  alignedIntersectedCharList.begin()->contactNormal;

 // 캡슐을 미끄러짐 벡터를 더한것으로 재정의한다.
 saengine::Capsule src( *m_pSceneObject->GetPosition(),
  *m_pSceneObject->GetPosition()+slidingPos,
  m_collisionSphere.radius );
...
...



네번째.
각 캐릭터들을 Sphere & Sphere로 처리하고 속도 성분을 투입하였다.

바로 이것이 나의 종착지 였다. 양쪽 Sphere에 속도 성분을 각각 투입해 속도를 고려하는 충돌처리를 한 것.
그리고 물리엔진을 만들어봤던 나로써는 큰 실수를 했었는데 한번 충돌했던 캐릭터는 다시 충돌처리를 하지 않을것이다. 라고 생각했던 것이다. 충돌하더라도 다시 처음부터 충돌처리를 해야한다. 때문에 무한루프로 빠질 수 있는데, 특수한 변수를 넣어서 방지해야한다.

 const INT MAX_PROCESSING_COUNT = 5;
 INT currProcessingCount = 0;

 D3DXVECTOR3 currPosition = *m_pSceneObject->GetPosition();
 D3DXVECTOR3 currDeltaPosition = deltaPosition;
 BOOL isCollision = FALSE;
 // 행위자의 충돌체
 saengine::Sphere srcBound(currPosition+m_collisionSphere.center,
  m_collisionSphere.radius );
 // 시간간격
 FLOAT elapsed = static_cast<CGameStage*>(g_pSAMgr->GetSceneStage())->GetCurrElapsed();
 // MAX_PROCESSING_COUNT만큼의 사이클을 돌린다.
 // 중요하다. 자칫 무한루프로 빠질수도 있기 때문에 최대횟수를 제한해야 한다.
 while( currProcessingCount < MAX_PROCESSING_COUNT )
 {
  BOOL isCollisionThisProcess = FALSE;
  std::vector<CCharacter*>* pCharList = m_pObjectManager->GetAllCharacterList();
  std::vector<CCharacter*>::iterator it = pCharList->begin();
  while( it != pCharList->end() )
  {
   CCharacter* pDestChar = *it++;
   if( pDestChar == this ) continue;
   
   // 대상의 충돌체
   saengine::Sphere destBound( pDestChar->GetPosition()+pDestChar->GetCollisionSphere().center,
    pDestChar->GetCollisionSphere().radius );
   D3DXVECTOR3 destDeltaPosition = pDestChar->GetVelocity()*elapsed;
   D3DXVECTOR3 contactNormal;
   FLOAT dist;
   FLOAT pen;
   if( CCollisionDetector::TestMovingIntersectSphereAndSphere(
      srcBound, destBound, currDeltaPosition, destDeltaPosition, &contactNormal, &dist, &pen ) )
   {
    if( m_pBrain ) m_pBrain->HandleMessage( MSG_COLLISION_WITH_CHARACTER, pDestCharacter, 4 );
    isCollisionThisProcess = TRUE;

    // 속도의 방향을 미끄러지는 방향으로 간단히 바꿔준다.
    // (V-(V*N)N)
    m_vecVelocity = m_vecVelocity-
     D3DXVec3Dot( &m_vecVelocity, &contactNormal ) * contactNormal;
    // 부딪혔으니 튕겨나가야한다.
    D3DXVECTOR3 resolveDeltaPos = currDeltaPosition+(contactNormal*pen);
    // 부딪히고 남은 거리를 계산한다.
    FLOAT remainingLength = D3DXVec3Length( &resolveDeltaPos )-dist;
    if( remainingLength <= 0.0f )
    {
     m_pSceneObject->SetPosition( currPosition );
     return;
    }
    // 앞으로 전진할 델타포지션을 계산한다.
    currDeltaPosition = resolveDeltaPos+(m_vecVelocity*elapsed);
    currPosition += currDeltaPosition;
    // 행위자의 충돌체를 업데이트시킨다.
    srcBound.center = currPosition+m_collisionSphere.center;
   }
  }
  if( !isCollisionThisProcess )
  {
   m_vecPrevPosition = *m_pSceneObject->GetPosition();
   m_pSceneObject->AddPosition( currDeltaPosition );
   break;
  }
  currProcessingCount++;
 }

'작업일지' 카테고리의 다른 글

모델이 깨지는경우  (0) 2010.07.16
오리엔테이션 생성중 실수.  (0) 2010.07.05
그림자 처리..  (0) 2010.06.26
Perspective Shadow Map..  (0) 2010.06.22
2010. 7. 5. 02:24

오리엔테이션 생성중 실수.

랜덤함수를 활용할때 한번더 생각해보고 하자.
전에 데미지를 추출할때도 0으로 나누는 에러가 났었다. 그때도 랜덤함수때문에..

pGoblin->GetSceneObject()->LookAt( D3DXVECTOR3( 0, 0, 0 ), D3DXVECTOR3( rand()%10-5, 0, rand()%10-5 ), D3DXVECTOR3( saengine::COrientation::Y_AXIS ) );

첫번째 파라메터는 위치, 두번째 파라메터는 바라보는 위치, 세번째 파라메터는 업벡터를 뜻하는데
두번째 파라메터를 보면 저 처리대로라면 0, 0, 0이 나올 수 있다.
그러니까.. 위치와 바라보는 위치가 똑같아 지고, 결과적으로 업벡터와 측면벡터가 0,0,0이 나오는 사태가 벌어지게 된다. 그래서 간단하게 아래와 같이 바꾸었다.

INT targetX = rand()%10-5;
 INT targetZ = rand()%10-5;

 if( targetX + targetZ == 0 ) targetX = 1;
 
 pGoblin->GetSceneObject()->LookAt( D3DXVECTOR3( 0, 0, 0 ),
  D3DXVECTOR3( targetX, 0, targetZ ), D3DXVECTOR3( saengine::COrientation::Y_AXIS ) );

허나 무엇보다 중요한건 엔진자체에 assert처리를 해야하는 점이었다..
비록 프로그래밍은 잘못하더라도 에러는 나야 정상아닌가 싶어 수정하기로 했다.
그래서 아래와 같이 수정해주었다.

VOID COrientation::SetDirection( const D3DXVECTOR3& viewDir, const D3DXVECTOR3& upDir, const D3DXVECTOR3& rightDir )
{
 // 오브젝트의 월드내 방향이다.
 m_vecViewDir = viewDir;
 m_vecUpDir  = upDir;
 m_vecRightDir = rightDir;
 NormalizeDirections();
 debug_assert(
  !( m_vecViewDir.x == 0.0f && m_vecViewDir.y == 0.0f && m_vecViewDir.z == 0.0f ) &&
  !( m_vecUpDir.x == 0.0f && m_vecUpDir.y == 0.0f && m_vecUpDir.z == 0.0f ) &&
  !( m_vecRightDir.x == 0.0f && m_vecRightDir.y == 0.0f && m_vecRightDir.z == 0.0f ),
  _T("제로벡터 에러") );

.....


 

'작업일지' 카테고리의 다른 글

모델이 깨지는경우  (0) 2010.07.16
캐릭터 충돌처리  (1) 2010.07.07
그림자 처리..  (0) 2010.06.26
Perspective Shadow Map..  (0) 2010.06.22
2010. 6. 27. 05:19

VAX를 활용한 헤더중복 #ifndef블럭 자동으로 생성하기

참고로 클래스뷰에서 클래스를 생성하면 #pragma once 가 자동으로 입력됩니다~ 참고하세요.


새로운 헤더를 만들면 항상하게 되는 작업은 바로 헤더중복을 방지하기 위한 #ifndef블록이다.
CTest라는 클래스와 함께 헤더, 소스파일을 생성한다면 파일을 생성함과 동시에 아래 구문을 써넣어야된다.

#ifndef _CTEST_H_ 
#define _CTEST_H_
...
#endif

작업량이 많을 때 매번 이러는 것은 상당히 귀찮은 작업임이 틀림없는데.. 한편 VAX에서의 VA Snippets를 활용하면 이를 간단하게 해결할 수 있다.


아래처럼 VAX의 옵션에 들어가서 Advanced > Suggestions > Edit VA Snippets(또는 Shift+마우스오른클릭 후 Edit VA Snippets)를 누른다.

snippets편집창이 뜨면 New를 눌러서 다음과 같은 식으로 추가한다. 여기서 Title에 타이틀명을 적어줘야 소스파일에서 오른쪽 클릭으로 자동삽입 텍스트 창을 띄울 수 있다.(이따 또 설명) 물론 한글도 가능하다.

여기서 $...$은 일종의 매크로인데, http://www.wholetomato.com/products/features/vasnippets.asp 에 가보면 여러가지 매크로와 기타 정보등을 얻을 수 있다. (아래 적어놓겠음)

참고로 자동텍스트를 추가하는 방법이 2가지가 있는데 하나는 Insert Snippets이고 하나는 Surround With ...이다. 전자는 문자열을 추가한 후, 바로 뒤에 커서가 위치하게 되고, 후자는 블록을 지정한 상태라면 selected$부분에 해당 블록부분이 들어간 후, 코드가 끝난뒤에 커서가 위치하게 되고 블록을 지정하지 않은 상태라면 $selected$부분에 커서가 위치하게 된다. 이를 구분하는 방법은 코드중 $selected$가 있느냐 없느냐에 따라 나뉜다.

위와 같이 입력한 후 소스파일상으로 돌아와서 아무곳에서나 오른쪽 클릭하면 왼쪽 메뉴가 뜨는데, Surround With .. 하위 메뉴에 들어가보면 방금 등록한 것이 새로 생겼음을 확인할 수 있다.(코드중 $selected$가 존재한다면 Surround With... 메뉴에 나오고 그외에는 VAX메뉴의 Insert Snippets..컨텍스트 메뉴(Shift+마우스오른클릭)로 해야한다. 아무튼 고걸 누르면...

아래와 같이 일련의 텍스트가 자동으로 입력된다. (커서가 있는 방향이 $selected$ 매크로인 것을 알 수 있다.)참고로 파일 이름은 RootPurposeProcess.h였다. 아까도 언급했었는데, 메뉴의 엔트리에 들어가려면 Title에 꼭 어떤 문자를 입력해서 넣어줘야한다. 넣지 않고서 그냥 short cut만 등록하면, 엔트리엔 존재하지 않지만 내부적으로 특정 문자열을 캐치해서 자동으로 넣어준다. for를 입력할때 괄호가 생기는 것처럼...


여담이지만, 위 메뉴에서 우리가 추가했던 SA project guard header의 순서를 제일 위로 올릴 수도 있는데, Edit VA Snippets의 왼쪽 엔트리리스트에서 그냥 SA project guard header를 클릭 드래그해서 제일 위로 올려주기만 하면된다. 

아래는 VAX홈페이지에서 발췌해온 매크로표이다. 

  Reserved String Meaning
Date $DATE$ Year/month/day formatted as %04d/%02d/%02d
$DAY$ Day of month formatted as %d
$DAY_02$ Day of month formatted as %02d
$DAYNAME$ Three-character abbreviation of day
$DAYLONGNAME$ Full name of day
$MONTH$ Month formatted as %d
$MONTH_02$ Month formatted as %02d
$MONTHNAME$ Three-character abbreviation of month
$MONTHLONGNAME$ Full name of month
$YEAR$ Year formatted as %d
$YEAR_02$ Year formatted as %02d
Time $HOUR$ Hour formatted as %d
$HOUR_02$ Hour formatted as %02d
$MINUTE$ Minute formatted as %02d
$SECOND$ Second formatted as %02d
File $FILE$ Full filename with path*
$FILE_UPPER$ Full filename with path in uppercase*
$FILE_BASE$ Filename without path or extension*
$FILE_BASE_UPPER$ Filename without path or extension in upper case*
$FILE_EXT$ Filename extension*
$FILE_EXT_UPPER$ Filename extension in upper case*
$FILE_PATH$ Path of file*
$FILE_PATH_UPPER$ Path of file in upper case*
General $clipboard$ Current clipboard
$end$ Position of caret after expansion
$selected$ Current selection**
$$ Literal '$' character
Symbol Context $MethodName$ Name of containing method
$MethodArgs$ Method parameters
$ClassName$ Name of containing class
$BaseClassName$ Name of base class of containing class
$NamespaceName$ Fully qualified namespace name
GUID $GUID_DEFINITION$ Generated GUID formatted for use in a definition
$GUID_STRING$ Generated GUID formatted for use in a string
$GUID_STRUCT$ Generated GUID formatted for use in a struct
The following reserved strings are available only in refactoring snippets
Refactor $GeneratedPropertyName$ Property name generated during Encapsulate Field
$generatedPropertyName$ Same as $GeneratedPropertyName$ but with lower-case first letter
$MethodArg$ One parameter of the method and its type
$MethodArgName$ One parameter of the method
$MethodArgType$ Type of one parameter of the method
$MethodBody$ Body of implementation
$MethodQualifier$ Optional qualifiers of method
$ParameterList$ Parameters separated by commas
$SymbolContext$ Context and name of method
$SymbolName$ Name of method
$SymbolPrivileges$ Access of method
$SymbolStatic$ Keyword static or blank
$SymbolType$ Return type of method
$SymbolVirtual$ Keyword virtual or blank

 

  

솔직히 해당 블록을 감싸서 for문으로 만든다던지.. 하는 것은 사실상 거의 필요없다. 주석처리하는 경우는 많지만.. 다른 단축키도 얼마든지 많기 때문에.. 하지만 파일정보에 대한 것이라던지.. 위와같이 헤더중복방지블록을 만든다던지 이런경우에는 매우 유용하기 때문에 이쪽으로 응용을 해본다면 정신건강에 매우 도움이 될것같다.

기본으로 등록되어있는 것중에 Win32 standard application도 있는데.. 요건.. 윈도우 하나를 깔끔하게 띄워주는 소스를 자동으로 입력한다. 같은 방법으로 Win32 dialog application을 하나 등록해놓고 다이알로그를 띄우는 것도 만들어낼 수 있겠당. 

2010. 6. 26. 03:29

그림자 처리..

재밌네요. 자세히 파니까 정말 여러가지 많이 배우는 것 같습니다.
아래는 LisPSM으로 구현했습니다. PSM보다 신경쓸게 의외로 없더군요. 쉽게 했습니다.

참고한 소스는 NVIDIA의 샘플입니다. 한편 http://www.cg.tuwien.ac.at/research/vr/lispsm/
의 논문에서의 구현내용과 조금 다른것 같더군요. 대충 보니까 일반적인 섀도우맵을 알고리즘에서.. 라이트위치에서의 Y축 투영으로 보이는데.. 이 알고리즘이 NVIDIA의 샘플하곤 다소 차이가 나는듯 보였습니다. NVIDIA의 샘플에선 뷰공간으로 변환한 뒤 광원방향을 UP벡터로 하는 가상의 카메라를 생성 후 섀도우 매핑하는 건데요. 논문에서는 조금 다르네요. 흐음 자세히 읽어봐야겠습니다.

'작업일지' 카테고리의 다른 글

모델이 깨지는경우  (0) 2010.07.16
캐릭터 충돌처리  (1) 2010.07.07
오리엔테이션 생성중 실수.  (0) 2010.07.05
Perspective Shadow Map..  (0) 2010.06.22
2010. 6. 25. 14:03

단기간에 많은 처리를 하는 선행조건을 가지는 함수의 분리


선행학습으로 인라인함수(Efficient C++)과 80-20의 법칙(Efficient C++, More Effective C++, 기타)을 읽어보시면 이해가 빠르십니다. 


어떤 함수가 처리되는데 특정한 조건이 필요한 경우를 보자. 그런데 그 함수가 단시간에 자주 호출된다면, 명령어 하나라도 최적화할 필요가 있다.

파란색 - CQuadTreeNode의 소스
연두색 - CQuadTree의 소스


예를 들어 쿼트트리내 오브젝트들을 가져오는 경우 해당 노드에 다음과 같은 함수를 호출할 수 있다.

VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum );

위 함수는 노드내 오브젝트와 절두체의 충돌판정 후 절두체와 충돌한다면 해당 오브젝트의 렌더링 콜백함수를 호출하는 함수로써 다음과 같은 형식을 가지고 있다. 하지만 선행조건이 걸려있다. m_iObjectCount <= 0, 오브젝트의 개수가 0보다 같거나 작다면 더이상 진행하지 않고 함수를 끝낸다.

CQuadTreeNode
VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
  if( m_iObjectCount <= 0 )
  {
      return;
  }

  for( SCENENODELIST::iterator it = m_sceneNodeList.begin() ;
     it != m_sceneNodeList.end() ;
     it++ )
  { 
     if( pFrustum->IsAABBInFrustum(*(*it)->GetWorldBoundAABB()) )
       (*it)->AddedRenderEntryArrayCallBack();
  }
}


이 함수는 쿼드트리클래스의 CQuadTree::CheckInFrustumAndNotifyToNode함수에서 다음과 같은 형식으로 호출될 수 있다.

CQuadTree
VOID CQuadTree::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
    // 쿼드트리의 영역 계산
    ...

    for 쿼드트리의 영역 순회
    {
       CQuadTreeNode* node = GetNode( .. );
       node->CheckInFrustumAndNotifyToNode( pFrustum );
    }
}


렌더링 쿼드트리의 특성상 아주 작은 시간에도 매우 자주 호출됨에 따라 단 하나의 명령어도 간과해서는 안된다. 즉, CQuadTreeNode::CheckInFrustumAndNotifyToNode 함수의 함수호출 명령어조차도 최소화 시켜야하는데 이 얘기는 inline으로 선언해야한단 얘기다. 하지만 inline으로 하기에는 소스의 길이가 다소 길다.(싱글톤메소드라고 해서 예외사항은 있다.) 만약 위 소스는 절대 inline이 될 수 없다 가정하면,
오브젝트의 개수 부분만을 인라인화 해서 아예 이 함수가 호출되지 않게끔 방지할 순 있다
. 다음을 보자. 수정된 부분은 굵은글씨로 표시해두었다.
 
CQuadTreeNode
inline BOOL CQuadTreeNode::IsExistNode()
{
  return m_iObjectCount>0 ? TRUE:FALSE;
}

VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
  // if검사부분을 아예 없애버렸다.
  // 이 경우는 다소 위험한 방법이긴한데 릴리즈시 약간의 오버헤드를 없앨 수 있다.
  // 만약, CQuadTree::CheckInFrustumAndNotifyToNode함수에서만 호출된다면,
  // 아래 코드는 99%안전한 코드다.
  // 특정 위치의 한곳에서만 호출되는 것을 싱글톤 메소드라고 하는데 이 경우는 FORCEINLINE 매크로로
  // 강제 인라인화 할 수 있다.(VS에서는 지나치게 긴 메소드를 인라인화에서 제외시킨다.)
  assert( m_iObjectCount > 0 && "오브젝트가 하나도 없는데 호출됐네요." );

  for( SCENENODELIST::iterator it = m_sceneNodeList.begin() ;
     it != m_sceneNodeList.end() ;
     it++ )
  { 
    if( (*it)->IsInFrustum( pFrustum ) )
       (*it)->AddedRenderEntryArrayCallBack();
  }


그리고 쿼드트리에서도 약간의 수정이 필요하다. 수정된 부분은 굵은글씨로 표시해두었다.

CQuadTree
VOID CQuadTree::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
    // 쿼드트리의 영역 계산
    ...

    for 쿼드트리의 영역 순회
    {
       CQuadTreeNode* node = GetNode( .. );
       if( node->IsExistNode() )
         node->CheckInFrustumAndNotifyToNode( pFrustum );
    }
}



성능이 비약적으로 상승한다.

'C/C++ > 패턴 및 리팩토링' 카테고리의 다른 글

파일간 #include 설계에 대한 생각  (0) 2010.07.07