2014년 3월 3일 월요일

[일기]Box2D, RayCast, 광선 계산.

RayCast, 번역을 어찌할 지 몰라 그냥 레이캐스트라 쓸까라고도 했으나 내가 이해가 안 되어 광선 계산(탐지, 추적)이라 표현하였다.

 이를 이용하는 과정에서는 직선광을 방향과 거리를 지정하여 사출시키고 이 직선광에 충돌하는 고정체에 대한 정보를 구하는 절차가 포함되게 된다.

 정보란, 거리, 법선벡터가 주를 이룬다.

 광선 탐지는 한 물체가 사출되는 광선으로부터 이와 접촉하는지를 판단할 때 쓰인다. 특히  접촉할경우 광선 시발점과 접촉점 사이의 거리를 구하기 위해서도 주로 쓰인다.

 사실 광선이 고정체에 접촉한다고 표현하였으나 메서드는 이를 주객을 바꾸어 개념을 정의한다. 고정체가 광선과 접촉하느냐로 시점을 바꾼다.

가령 광선->충돌하는가(고정체) 가 아니라 고정체->광선에충돌하는가(광선,...) 의 방식이다.

 광선 계산을 위한 메서드는 다음과 같다.

 bool b2Fixture::RayCast(b2RayCastOutput* output, const b2RayCastInput& input, int32 num); //셋째 인자는 무슨 영문인지는 모르지만, 존재해도 사용되지 않는다. 불편하다.

입력에 해당되는 input은 다음과 같은 멤버를 갖는 구조체이다.

struct b2RayCastInput
  {
      b2Vec2 p1, p2;
      float32 maxFraction;
  };

p1은 발사할 시작 위치, p2는 도착 위치이다. 이 기본적인 p2와 p1 사이의 거리를 1로 규정하며, maxFraction은 이 거리에 곱해질 수치로, 최종적인 광선의 길이는 p2와 p1 사이의 거리에 이 값을 곱한 값으로 결정된다.

출력에 해당되는 output은 다음과 같은 멤버를 갖는 구조체이다.

struct b2RayCastOutput
  {
      b2Vec2 normal;
      float32 fraction;
  };

normal은 충돌 지점의 단위법선벡터로 크기가 1이다.
fraction은 충돌한 광선이 실제 p2에서 p1 사이의 거리에 얼마를 곱해야 자신과 만나는지를 알려주는 값이다.



[input 설명]


[output 설명]

후에 다시 참조하기 위해 연습한 코드를 기재한다.

#ifndef __MYRAYCASTINGTEST_H__
#define __MYRAYCASTINGTEST_H__

//광선이 계속 회전하도록 현재 각을 저장한다. (이후에 이 값을 증가시켜 각도를 적용한다)
float currentRayAngle = 0;

class MyRayCastingTest : public Test {
public:
MyRayCastingTest(){
b2BodyDef myBodyDef;
myBodyDef.type = b2_staticBody;
myBodyDef.position.Set(0, 0);
b2Body* staticBody = m_world->CreateBody(&myBodyDef);

b2PolygonShape polygonShape;
b2EdgeShape edgeShape;

b2FixtureDef myFixtureDef;
myFixtureDef.shape = &edgeShape;

b2Vec2 bl(-20, 0);
b2Vec2 br(20, 0);
b2Vec2 tl(-20, 40);
b2Vec2 tr(20, 40);

edgeShape.Set( bl, br );
staticBody->CreateFixture(&myFixtureDef);
edgeShape.Set( tl, tr );
staticBody->CreateFixture(&myFixtureDef);
edgeShape.Set( bl, tl );
staticBody->CreateFixture(&myFixtureDef);
edgeShape.Set( br, tr );
staticBody->CreateFixture(&myFixtureDef);

                //이상까지 사각형 영역을 생성한다.


                //아래에서 사각형과 원형의 동적 강체들을 추가한다.
myBodyDef.type = b2_dynamicBody;
myBodyDef.position.Set(0, 20);
polygonShape.SetAsBox(2, 2);
myFixtureDef.shape = &polygonShape;
myFixtureDef.density = 1;


for(int i = 0; i < 5; i++){
m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef);
}
b2CircleShape circleShape;
circleShape.m_radius = 2;
myFixtureDef.shape = &circleShape;
for(int i = 0; i <5; i++){
m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef);
}

                //움직임을 잘 살피기 위해 중력을 없앤다.
m_world->SetGravity( b2Vec2(0, 0) );


}

void Step(Settings* settings){
Test::Step(settings);

                //매번 각도를 증가시킨다.
currentRayAngle += 360 / 20.0 / 60.0 * DEGTORAD;

float rayLength = 25;
b2Vec2 p1(0, 20);
b2Vec2 p2 = p1 + rayLength * b2Vec2( sinf(currentRayAngle), cosf(currentRayAngle) );

//set up input

glColor3f(1,1,1); //white
                drawReflectedRay(p1, p2);                        //광선 계산한다. 고정체에 닿을 경우 반사되어 다음 고정체를 탐지한다. 이 과정은 광선의 최대 길이(maxFraction값을 곱한 크기)까지만 진행된다.

}

void drawReflectedRay( b2Vec2 p1, b2Vec2 p2 )
  {
      //set up input
      b2RayCastInput input;
      input.p1 = p1;
      input.p2 = p2;
      input.maxFraction = 1;

      //check every fixture of every body to find closest
     //이러한 순회는 좋지 않은 방법이나, 지금은 이와 같이 하였다.
     // 잘 살펴보면, 모든 고정체마다 광선 계산을 하고 있다. 일일이 접촉하는지를 체크해야 하기 때문이다.
      float closestFraction = 1; //start with end of line as p2
      b2Vec2 intersectionNormal(0,0);
      for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) {
          for (b2Fixture* f = b->GetFixtureList(); f; f = f->GetNext()) {

              b2RayCastOutput output;
              if ( ! f->RayCast( &output, input, 3 ) )
                  continue;
              if ( output.fraction < closestFraction ) {
                  closestFraction = output.fraction;
                  intersectionNormal = output.normal;
              }
          }
      }
      b2Vec2 intersectionPoint = p1 + closestFraction * (p2 - p1);

      //draw this part of the ray
      glBegin(GL_LINES);
      glVertex2f( p1.x, p1.y );
      glVertex2f( intersectionPoint.x, intersectionPoint.y );
      glEnd();

      if ( closestFraction == 1 )
          return; //ray hit nothing so we can finish here
      if ( closestFraction == 0 )
          return;

      //광선의 반사를 위해 직선에 대칭인 점을 구하는 공식이 사용되었다.
      b2Vec2 remainingRay = (p2 - intersectionPoint);
      b2Vec2 projectedOntoNormal = b2Dot(remainingRay, intersectionNormal) * intersectionNormal;
      b2Vec2 nextp2 = p2 - 2 * projectedOntoNormal;

      //recurse
      drawReflectedRay(intersectionPoint, nextp2);
  }

void MouseDown(const b2Vec2& p){
b2Vec2 pp = p;
pp.y = pp.y - 1;

Test::MouseDown(pp);
}

static Test* Create(){
return new MyRayCastingTest();
}
};

#endif

결과는
Raycasting

가운데가 광선의 시발점이다. 위로 가자마자 사각형에 접촉하고 여기서 반사되어 아래로 가다가 다시 아래 사각형의 우측 모서리 부근에서 반사되어 원에 충돌하고 .... 충돌하고... 하는 과정이 나타나 있다.

댓글 없음:

댓글 쓰기