Home [box2d] box2d 문서
Post
Cancel

[box2d] box2d 문서

Box2d

1. box2d 2.4.1

  • C++로 작성된 강체 시뮬레이션 라이브러리
  • b2라는 접두어의 타입을 가지고 있음.

1.1. object


  1. shape: 원이나 폴리곤 같은 2차원 기하객체
  2. rigid body: 물체를 구성하는 두입자의 거리가 절대로 바뀌지 않는 물체, 단순화된, 가정된 형태
  3. fixture: body에 shape를 연결, 밀도, 마찰, 반발력과 같은 물체 속성 추가
  4. constraint: bodies에서 자유도를 제거하는 물리적 연결.

    • 2d body: 2개의 이동축과 1개의 회전축. 즉, 3개의 자유도를 가진다.
    • 만약 body를 벽에 고정한다면 벽에 body를 constraint한것. 그 고정점을 기준으로 body를 회전만 시킬 수 있으므로 이는 2개의 자유도를 제거한것과 같다.
  5. constract constraint: 강체의 침투(강체가 부딪혔을 때 움푹파이는 정도)를 막기 위해, friction and restitution을 시뮬레이션 할 수 있게한다. (직접 만들 수 없고, Box2D가 알아서 생성)

  6. joint: 2가지 이상의 body를 하나로 만들어주는 constraint. Box2D는 revolute, prismatic, distance와 같은 joint 타입을 제공한다. 몇몇 joint는 limits and motors를 가지고 있다.
    • joint limit: 조인트의 움직이는 범위를 제한(사람의 팔꿈치 처럼)
    • joint motor: 조인트 모터는 조인트의 허용되는 각도에 따라 연결된 바디의 움직임을 유발한다. (모터를 팔꿈치를 회전시키기 위해 사용할 수 있다.)
  7. world: 프로그래밍상의 물리공간. 여러 물리공간을 만들 수 있지만 권장되지 않는다.

  8. solver: 물리공간은 solver를 가진다. 좀 더 나은 타이머를 사용할 수 있게 해주며, 접촉과 joint constraints을 해결해준다. (Box2D에서는 N개의 제약을 하나씩 수행하는 iterative solver를 가짐)
  9. continuous collision: solver는 물체를 점진적(discrete time steps)으로 이동시킴. 간섭(intervention)이 없으면 turnneling effect가 발생할 수 있음
    • turnneling effect: 입자가 벽을 넘어 마치 공이동을 하는것처럼 보이는것(파동의 성질)
    • Box2D에서는 이를 처리하기위해 특별한 알고리즘을 포함하고 있음.
    • collision 알고리즘: 두 body의 움직임을 보간(iterpolate) 하여 첫번째 충돌시간(TOI)를 찾는다. body를 첫 충돌시점으로 이동시킨다음 충돌을 해결하는 하위 단계 solver를 사용하여 해결

1.2. module


common -> collision -> dynamics

common -> dynamics

  • 3가지 모듈(Common, Collision, Dynamics)로 구성된다.
  • Common: allocation, mathm, settings와 관련된 코드를 가진다.
  • Collision: shapes, broad-phase, collision function/queries를 정의한다.
  • Dynamic: simulation world, bodies, fixture, joints를 제공.

1.3. Units


  • 부동소수점, tolerances(허용오차) 사용,
  • 허용오차는 미터, 킬로그램, 초(mks)단위에 잘 동작하도록 조정되어있다. - 그렇기 때문에 움직이는 물체의 크기는 대략 0.1~10미터 사이로 유지해야한다. - 환경(environment)과 actors를 렌더링할 때 스케일링 시스템을 사용해야한다. - Box2D테스트 베드는 OpenG 뷰포트 변환을 사용하여 이를 수행한다.

    주의! 픽셀을 한 단위로하면 잘못된 시뮬레이션과 이상한 동작으로 이어진다.

  • 특히 0.1~10 미터 사이에서 잘 동작한다.
  • static shapes은 50미터까지 잘 동작한다.
  • 전체 world size가 2km 보다 커지면 정밀도를 잃게되어 불안정해진다.

  • Box2D는 degrees가 아니라 radians을 사용한다. 즉, 바디의 회전은 라디안으로 값을 저장하고 증가 또는 축소함으로써 이루어진다.

  • length units을 변경하기위해서는 b2_lengthUnitsPerMeter를 수정해야한다. b2_user_settings.h에 있는 B2_USER_SETTING를 통해 병합충돌을 피할 수 있다. (b2_settings.h)

1.4. Factories and Definitions


  • 빠른 메모리관리는 Box2D 설계에서 핵심적인 역할을 한다.
  • 그래서 b2Body 또는 b2Joint 를 생성할 때, (b2World)[https://box2d.org/documentation/classb2_world.html]에서 factory functions을 호출할 필요가 있다.
1
2
3
4
5
b2Body* b2World::CreateBody(const b2BodyDef* def)
b2Joint* b2World::CreateJoint(const b2JointDef* def)
// <->
void b2World::DestroyBody(b2Body* body)
void b2World::DestroyJoint(b2Joint* joint)
  • body 또는 joint를 생성할 때, 구축하는데 필요한 모든 정보를 정의해서 제공해야한다.

    • 이를 통해 객체 생성에서의 오류를 방지할 수 있다.
    • 함수의 파라미터수를 줄일 수 있다.
    • 합리적인 기본값을 제공할 수 있다.
    • 접근자(accessors)의 수를 줄일 수 있다.
  • fixtures(shapes)는 body의 부모가 되어야하므로 b2Body에서 팩토리 매서드를 사용하여 생성 및 소며로딘다.

1
2
3
b2Fixture* b2Body::CreateFixture(const b2FixtureDef* def)
b2Fixture* b2Body::CreateFixture(const b2Shape* shape, float density)
void b2Body::DestroyFixture(b2Fixture* fixture)
  • Factories 는 정의에 대한 참조를 유지하지 않는다.
  • 따라서 스택에 정의를 생성하고 임시 리소스에 보관할 수 있다.

2. Hello Box2D

  • 커다란 large ground box와 small dynamic box에 대한 예제
  • 이 코드에는 그래픽이 포함되어있지 않다.
  • 시간에 따른 상자 위치의 콘솔에 텍스트 출력만 표시된다.

2.1. Creating a World


  • 모든 Box2D 프로그램은 b2World객체 생성으로 시작한다.
    • b2World: 메모리, 객체, 시뮬레이션을 관리하는 physics hub이다.
    • physics world를 스택, 힙, data section에 할당할 수 있다.
  • Box2D world를 생성하는 것은 아래와 같이 gravity vector를 정의하기만 하면 된다.(중력의 방향과 중력값)
1
2
b2Vec2 gravity(0.0f, -10.0f);
b2World world(gravity);

주의! scope를 조심해야한다. (할당과 해제)

2.2. Creating a GroundBox


  • Bodies 는 다음과 같은 단계를 거쳐 구축된다.
    1. body의 position, damiping, etc. 을 정의
    2. world object를 사용하여 body 생성
    3. fixtures 정의 (shape, friction, density, etc.)
    4. fixtures을 body에 생성(붙임)

2.2.1. step1

  • ground body 생성해야한다.
  • 이를 위해 body정의가 필요하다.
  • body 정의를 사용하여 ground body의 초기 position를 지정해야한다.
1
2
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0.0f, -10.0f);

2.2.2. step2

  • body 정의를 world object로 넘겨 ground body를 생성한다.
  • world object는 body정의에 대한 참조를 유지하지 않는다.
    • body는 기본적으로 static이다.
    • static bodies는 다른 static bodies과 충돌하지 않으며 움직일 수 없다.
1
b2Body* groundBody = world.CreateBody(&groundBodyDef);

2.2.3. step3

  • ground polygon을 생성해야한다.
  • SetAsBox shortcut을 사용하여 ground polygon을 box shape로 형성하고, box가 부모 몸체의 원점에 중심이 되도록해야한다.
1
2
b2PolygonShape groundBox;
groundBox.SetAsBox(50.0f, 10.0f);
  • setAsBox는 half width 그리고 half height (extents를 인자로 받는다.
  • 즉, 위 코드는 100 units width(x-axis), 20units tall(y-axis)를 가지는 ground box인 것이다.
    • box2D는 mks 단위로 조정됬으므로, extents를 meters로 고려할 수 있다.
    • Box2D는 일반적으로 객체가 일반적인 실제 객체 크기일 때 가장 잘 작동한다. (ex. box2d를 사용하여 glaciers, dust 파티클의 움직임을 모델링하는것은 좋은 생각이 아님)

2.2.4. step4

  • shape fixture를 생성하는것이 마지막 단계이다.
  • 간단한 방법으로 fixture 기본값을 사용하는것이다.
  • 두번째 파라미터는 shape의 입방미터당 킬로그램이며 밀도를 나타낸다.
  • static body는 기본값으로 0이다.
1
groundBody->CreateFixture(&groundBox, 0.0f);

box2D는 shape에 대한 참조를 유지하지 않는다. data를 새로운 b2Shape 객체로 복사(clone)한다.

2.2.5. about fixture

  • 모든 fixture는 부모 body가 있어야한다. (static 이더라도) 하지만, 모든 static fixtures를 single static body에 붙일 수 있다.

  • fixture를 사용하여 body에 shape를 붙일 때, shape의 좌표는 body의 local이 된다. 그래서 body가 움직이면, shape 또한 움직인다. fixtures의 world transform은 부모 body에서 상속받는다.

  • fixture는 body 와 독립된 transform 을 가지고 있지 않다.

  • 그래서 body에서 shape를 움직이지 않는다. body에 있는 모양을 이동하거나 수정하는 것은 지원되지 않는다.

    • 이유: 모양이 변형되는 body는 강체(rigid body) 가 아니지만, box2D는 rigidbody engine이다. Bodx2D에서 만들어진 많은 가정은 강체 모델을 기반이므로, 이를 위반하면 많은것들이 제대로 작동하지 않게된다.

2.3. Creating a Dynamic Body


  • ground body를 만든 것처럼 똑같이 dynamic body를 만들 수 있다.
  • 치수를 제외한 차이점은 dynamic body의 mass 속성을 설정해야한다는 것이다.

  • 첫번째로 CreateBody 를 사용하여 body를 생성해야한다.
  • 기본적으로 bodies는 static이므로 b2BodyType을 설정해야한다.
1
2
3
4
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);

Caution: You must set the body type to b2_dynamicBody if you want the body to move in response to forces.

  • 다음으로 fixture 정의를 통해 polygon shape를 생성하고 달아줘야한다.
1
2
3
4
5
6
7
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(1.0f, 1.0f);

b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
  • 여기서 주목할 점은 density가 1(default = 0) 그리고 friction을 0.3f로 설정한 점이다.

Caution: A dynamic body should have at least one fixture with a non-zero density. Otherwise you will get strange behavior.

  • 이제 fixture 정의를 가지고 fixture를 생성할 수 있다.
  • 이 때 자동적으로 body의 mass가 업데이트된다.
  • 또한, 다수의 fixtures를 추가할 수 있다. 이 경우 각각은 전체 mass에 기여한다.
1
body->CreateFixture(&fixtureDef);

2.4. Simulating the world


  • ground box 그리고 dynamic box를 초기화했으므로, 이제 시뮬레이션 할 준비만 몇가지 하면 된다.

2.4.1. time step

  • Box2D는 적분기(integrator) 라는 컴퓨팅 알고리즘을 이용한다.
    • 적분기: 물리 방정식을 이산시간에 대해서 시뮬레이션하는 알고리즘
      • 이산시간: 시간을 일정한 간격으로 잘라 사용하는 것을 의미
  • 이산시간은 전통적인 게임루프(플립북에서 대상이 움직이는 하나의 scene)과 같이 진행된다. 그래서 Box2D를 위한 time step이 필요하다.
  • 일반적으로 게임에서 물리엔진은 60프레임, 60Hz 또는 1/60초 만큼 빠른 time step을 사용한다.
  • 더 큰 time steps를 설정할 수 있지만, 이 경우 생성한 world 객체에 대한 정의를 주의깊게 준비해야한다.
  • time steps는 고정된 것이 좋다. 가변 time step은 다양한 결과를 야기시킨다. 이것은 디버깅하기도 어려우므로, 진행하는 프레임에 time step을 엮는것은 권장되지 않는다.
  • 아래와 같이 간단하게 time step을 설정할 수 있다.
1
float timeStep = 1.0f / 60.0f;

2.4.2. constraint solver

  • 추가적으로 적분기에 constraint solver를 이용할 수 있다.

    • constraint solver: 시뮬레이션 constraint에 대해서 해결책을 제공. 하나의 constraint은 완전하게 해결 할 수 있다. 그러나 하나의 constraint을 해결할 때 또 다른 제한이 방해할 수 있다. 좋은 해결책은 모든 제한에 대해서 여러번 반복하는것.
  • constraint solver의 2개의 단계

    • 속도 단계(velocity phase): solver는 body를 정확하게 움직이기 위해 충격(impulses)을 계산한다.
    • 위치 단계(position phase): solver는 겹침(overlap) 과 조인트 분리의 오차를 줄이기 위해서 body의 position을 조정한다.
    • 각각의 단계는 자기자신의 iteration count를 가지고 있으며, 위치 단계에서는 만약 오차가 작다면 반복을 일찍 종료한다.
  • Box2D는 속도 단계의 iteration count는 8을 권장하며, 위치 단계의 iteration count는 3을 권장한다.
  • 이를 조정하는 것은 성능과 정확성 사이의 trade-off 임을 알아야한다.
    • 적은 숫자: 속도를 증가, 정확도는 감소
  • 간단한 예제이므로 아래와 같이 설정하였다.
1
2
int32 velocityIterations = 6;
int32 positionIterations = 2;
  • time step 과 iteration count는 완전히 상관이 없으며, 한 iteration은 sub-step이 아니다.
  • 하나의 solver iteration은 time step내의 모든 constraint를 한번씩은 통과해야한다. (최소한 한번)

2.4.3. simulation loop

  • 시뮬레이션 루프는 게임루프에 통합된다.
  • 게임 루프 전체의 각각의 단계에서 b2World::step을 호출할 수 있다.
  • 일반적으로 게임 루프 프레임과 물리 time step에 의존하기 때문에 상황마다 다르지만 일반적으로 한번만 호출하면 충분하다.

  • 이 예제는 그래픽이 없고, 아래의 코드는 dynamic body의 위치와 회전을 출력한다. 아래는 총 1 초의 시뮬레이션 시간 동안 60 개의 시간 단계를 시뮬레이션하는 시뮬레이션 루프이다.
1
2
3
4
5
6
7
for (int32 i = 0; i < 60; ++i)
{
    world.Step(timeStep, velocityIterations, positionIterations);
    b2Vec2 position = body->GetPosition();
    float angle = body->GetAngle();
    printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle);
}
  • 출력 결과는 상자가 떨어지고 ground box에 착륙하는 것을 보여준다
1
2
3
4
5
6
7
0.00 4.00 0.00
0.00 3.99 0.00
0.00 3.98 0.00
...
0.00 1.25 0.00
0.00 1.13 0.00
0.00 1.01 0.00

2.5. Cleanup


  • world 가 scope를 벗어나게되거나 포인터에서 delete가 호출되어 삭제될 때, 모든 bodies, fixtures, joint는 메모리에서 해제된다.
  • 이처럼 무효화되기 때문에. world의 객체들을 가리키는 모든 포인터들을 잘 관리해야한다.
  • 이를 통해 성능을 얻었고, 코드도 간결해졌다.

3. Common Module

  • settings, memory management, vector math

3.1. settings

  • box2D에 정의 된 타입들을 통해 구조의 크기를 쉽게 결정가능하다.
  • b2Settings.h 에 정의된 상수들, 일반적으로 조정할 필요가 없다.
  • 부동소수점 연산을 하기 때문에 round-off 오류를 위한 수치 허용오차가 정의되어있음 (일부는 상대적, 절대적 허용오차는 MKS단위 사용)
  • b2Version 구조체로 box2d의 현재 버전을 알아낼 수 있다.

3.2. Memory Management

  • 많은 수의 작은 객체(50-300바이트)를 할당하는 경향이 있는 box2d.
  • 작은 객체에 대해 malloc, new 를 통해 시스템 힙을 사용하는 것은 비효율적이며 단편화를 일으킬 수 있다.
  • 이러한 객체에 대한 힙 메모리를 효율적으로 제공할 수 있는 할당자가 필요
  • small object allocator (SOA): b2BlockAllocator
    • SOA는 다양한 크기의 확장 가능한 여러 풀을유지한다, 메모리에 대한 요청이 이루어지면 SOA는 요청된 크기에 가장 적합한 메모리 블록을 반환한다. 블록이 해제되면 풀로 반환된다. 이러한 작업은 모두 빠르며 힙 트래픽이 발생하지 않는다.
  • Box2D는 SOA를 사용하기 때문에 절대로 body, fixture, joint를 new와 malloc을 하면 안된다.(하지만 b2World는 할당 가능)

    • b2World: body, fixture, joint를 생성할 수 있는 factories 제공하는 클래스
  • [참고] time step 실행도중 Box2D는 임시 작업 공간 메모리가 필요하다. 그러므로 b2StackAllocator 라는 스택할당자를 사용하여 단계별 힙 할당을 방지한다. (스택할당자와 상호작용할 필요 없다.)

3.3. Math

  • 간단한 벡터, 행렬 연산 모듈을 포함한다.
  • Box2D 내부 요구 사항에 맞게 설계되어있다.

4. Collision Module

  • shapes, dynamic tree, broad-phase(큰 시스템에서 충돌처리 가속을 위한)

4.1. shape

  • 충돌 기하학을 설명, 물리 시뮬레이션과 독립적으로 사용됨.
  • 최소한 rigid bodies과 어떻게 연결되는지 방법을 이해애야한다.
  • b2Shape 클래스로 구현되어있고, 아래와 같은 함수들이 정의되어있다.
    • shape가 겹쳤는지 테스트.
    • shape에 대한 ray cast 수행
    • shape의 AABB 계산
    • shape의 mass 속성 계산
  • 게다가, 각각의 shape는 type member, radius가 있다.
    • radius: 다각형(polygons)에도 적용가능.
  • shape는 body에 대한 정보가 없으며, dynamics system과 별개이다.
  • shape는 크기와 성능에 최적화된 컴팩트한 형태로 저장된다.
  • 따라서 shape는 쉽게 움직이지 않는다. - 수동적으로 정점 위치를 설정해야한다. - 하지만 fixture를 사용하여 형상을 body에 붙이면, shape는 host body와 함께 이동한다.

shape가 body에 연결되지 않은 경우, 그것의 정점이 world-space로 표현된다.

shape가 body에 연결되면, 해당 꼭짓점이 local-coordinates 표현된다.

4.1.1. Circle Shapes

  • circle 은 position 과 radius 를 가지고 있다.
  • circle 로 속이 빈 원을 만들 수 없다.
1
2
3
b2CircleShape circle;
circle.m_p.Set(2.0f, 3.0f);
circle.m_radius = 0.5f;

4.1.2. PolyGon Shapes

  • Polygon 은 convex polygons이다. (볼록 다각형)
  • Polygon은 속이 비어있지 않는다.
  • 다각형에는 3개 이상의 꼭짓점이 필요하다.
  • 다각형의 정점은 CCW(반시계 당향)으로 저장된다.
    • ccw: z축이 평면 바깥쪽을 가리키는 오른손 좌표계에 관한 것.
    • 좌표계 규칙에 따라 화면에서 시계방향으로 나타날 수 있다.
  • 폴리곤 멤버는 public 이지만, 폴리곤을 생성하려면 초기화 함수를 사용해야한다.
    • 초기화 함수: 법선 벡터를 생성하고 유효성 검사를 수행
  • 꼭짓점 배열을 전달하여 다각형 모양을 만들 수 있다.
    • 배열의 최대 크기는 기본값이 8인 b2_maxPolygonVertices에 의해 제어된다.
  • b2PolygonShape::Set 함수는 convex hull을 자동으로 계산하고 적절히 감는 순서를 설정한다(winding order).
  • convex hull 함수는 정점을 제거하거나 재정렬할 수 있다. (b2_linearSlop 보다 가까운 정점은 병합되어진다.)
1
2
3
4
5
6
7
8
9
// This defines a triangle in CCW order.
b2Vec2 vertices[3];
vertices[0].Set(0.0f, 0.0f);
vertices[1].Set(1.0f, 0.0f);
vertices[2].Set(0.0f, 1.0f);

int32 count = 3;
b2PolygonShape polygon;
polygon.Set(vertices, count);
  • polygon shape 는 box를 생성하는 몇가지 편의 기능이 있다.
1
2
void SetAsBox(float hx, float hy);
void SetAsBox(float hx, float hy, const b2Vec2& center, float angle);
  • Polygons은 b2Shape의 radius를 상속한다.
  • radius는 polygon 주위에 skin을 만든다.
  • skin은 stacking scenarios에서 다각형을 약간 분리하는데 사용된다.
    • 이렇게 하면 코어 폴리곤에 대해 연속 충돌을 처리할 수 있다.
  • polygon skin은 polygon을 분리된 상태로 유지함으로써 tunneling을 방지한다.
  • 그 결과 두 shape 사이에 작은 간격이 생기게된다.
    • 이러한 간격을 숨기기위해 다각형이 보다 더 크게 시각적으로 표현할 수 있다.

Not that polygon skin is only provided to help with continuous collision. The purpose is not to simulate rounded polygons.

4.1.3. Edge Shapes

  • edge shape는 선분(line segments)이다.
  • 이는 게임을 위한 자유형식의 static 환경을 만드는데 도움이 되도록 제공된다.
  • 주요 제한은 원 및 다각형과 충돌할 수 있지만 edge와는 충돌할 수 없다는 것이다.
  • Box2D에서 사용하는 충돌 알고리즘은 충돌하는 두 모양 중 적어도 하나에 볼륨이 있어야한다.
    • 가장자리 모양에는 적어도 하나의 볼륨이 있어야한다.
      • edge에는 volume이 없으므로 edge-edge 충돌이 불가
1
2
3
4
5
6
// This an edge shape.
b2Vec2 v1(0.0f, 0.0f);
b2Vec2 v2(1.0f, 0.0f);

b2EdgeShape edge;
edge.SetTwoSided(v1, v2);
  • 많은 경우 게임 환경은 여러 edge shape의 끝을 연결하여 구성한다.
    • 이 경우 가장자리 체인을 따라 미끄러질 때 예기치 않은 artifact가 일어날 수 있다.
    • ghost collisions: polygon이 내부 충돌 법선(internal collision normal)을 생성하는 내부 정점과 충돌할 때 발생한다.
    • 다행히 edge shape는 인접한 ghost vertices를 저장하여 ghost collisions을 제거하는 방법을 제공한다.
  • 고스트 충돌을 처리하기 위한 box2D알고리즘은 단측 충돌만 지원한다.
    • ?첫번째 정점에서 두번째 정점을 볼때 정면이 오른쪽이며 ccw감기 순서와 일치한다.
1
2
3
4
5
6
7
8
// This is an edge shape with ghost vertices.
b2Vec2 v0(1.7f, 0.0f);
b2Vec2 v1(1.0f, 0.25f);
b2Vec2 v2(0.0f, 0.0f);
b2Vec2 v3(-1.7f, 0.4f);

b2EdgeShape edge;
edge.SetOneSided(v0, v1, v2, v3);
  • v3과 v0: 고스트
  • 일반적으로 이 방법은 사용안한다. (wasteful and tedious)
  • chain shapes를 사용한다.

4.1.4. Chain Shapes

  • 사슬 모양은 많은 edge를 함께 연결하여 static game worlds를 구성하는 효율적인 방법을 제공한다.
  • 사슬 모양은 자동적으로 ghost collisions를 제거하고 일방적인 충돌을 제공한다. (provide one-sided collision)
  • ghost collision을 신경쓰지 않는다면 그냥 edge 두개를 만들면 된다. (효율성은 비슷)
  • chain shape를 만들기 위한 가장 간단한 방법은 loops를 생성하는 것이다. 정점 배열을 제공하기만 하면된다.
1
2
3
4
5
6
7
8
b2Vec2 vs[4];
vs[0].Set(1.7f, 0.0f);
vs[1].Set(1.0f, 0.25f);
vs[2].Set(0.0f, 0.0f);
vs[3].Set(-1.7f, 0.4f);

b2ChainShape chain;
chain.CreateLoop(vs, 4);
  • edge normal은 감는 순서(winding order)에 따라 다르다.
    • 시계 반대 방향 순서는 법선을 바깥쪽으로 향하게 하고, 시계반대 방향 순서는 법선을 안쪽으로 향하게 한다.
  • scrolling game world에서 여러 체인을 함께 연결하고 싶은 경우
    • b2EdgeShape에서처럼 고스트 정점을 사용하여 체인을 연결할 수 있다.
1
2
b2ChainShape::CreateChain(const b2Vec2* vertices, int32 count,
        const b2Vec2& prevVertex, const b2Vec2& nextVertex);
  • 체인 모양은 자체 교차(self-intersection)을 지원하지 않는다. (작동할 수도 않을 수도 있다.)
    • 또한 매우 가까운 정점은 문제를 일으킬 수 있다.(모든 edge가 b2_linearSlop(5mm)보다 긴지 확인해야한다.)
  • 체인의 각 edge는 child shape로 처리되며 인덱스로 접근할 수 있다.
    • 체인이 body와 연결되면, 각 edge 는 넓은 위상 충돌 트리(broad-phase collision tree)에서 고유한 경계 box 를 가진다.
1
2
3
4
5
6
7
8
// Visit each child edge.
for (int32 i = 0; i \< chain.GetChildCount(); ++i)
{
    b2EdgeShape edge;
    chain.GetChildEdge(&edge, i);

    ...
}

4.2. Geometric queries

  • 단일 shape에 대해 여러 기하학적 쿼리(geometric queries)를 수행할 수 있다.

4.2.1. Shape Point Test

  • shape transform과 world point을 인자로 받아 shape가 겹치는지 테스트할 수 있다.
1
2
3
4
5
b2Transform transform;
transform.SetIdentity();
b2Vec2 point(5.0f, 2.0f);

bool hit = shape->TestPoint(transform, point);
  • edge와 chain은 항상 false (chain이 loop여도)

4.2.2. Shape Ray Cast

  • shape에 ray를 투사하여 첫 번째 교차점과 법선벡터를 얻을 수 있다. (first intersection and normal vector)
  • raycast가 한번에 하나의 edge만을 검사하기 때문에 chain shape에 child index가 포함된다.

    주의: ray가 convex shape 내부에서 시작할 경우, 아무것도 적중되지 않음. (solid 객체)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
b2Transfrom transform;
transform.SetIdentity();

b2RayCastInput input;
input.p1.Set(0.0f, 0.0f);
input.p2.Set(1.0f, 0.0f);
input.maxFraction = 1.0f;
int32 childIndex = 0;

b2RayCastOutput output;
bool hit = shape->RayCast(&output, input, transform, childIndex);

if (hit)
{
    b2Vec2 hitPoint = input.p1 + output.fraction * (input.p2 - input.p1);
    ...
}

4.3. Pairwise Functions

  • Collision 모듈에는 한 쌍의 shape를 취하고 일부 결과를 계산하는 함수가 포함되어있다.

4.3.1. Overlap

1
2
b2Transform xfA = ..., xfB = ...;
bool overlap = b2TestOverlap(shapeA, indexA, shapeB, indexB, xfA, xfB);
  • 체인 shape인 경우, child indices를 제공해야한다.

4.3.2. Contact manifolds

  • Box2D에는 overlapping shapes를 계산하는 기능이 있다.
  • 원-원 or 원-다각형 : 하나의 접점과 법선만 얻을 수 있다.
  • 다각형-다각형: 두점을 얻을 수 있다.
  • 이러한 점은 동일한 법선 벡터를 공유하므로, Box2D는 이들을 manifold structure로 그룹화한다.
    • 일반적으로 manifold structure를 직접 계산할 필요 x, 하지만 시뮬레이션에서 생성된 결과를 사용할 가능성이 높다,
  • contact solver는 이를 활용하여 stacking stability를 개선한다.

  • b2Manifold structure: 법선벡터와 최대 2개의 접점을 가진다.

    • 법선과 점은 local coordinates로 유지된다자
    • contact solver의 편의를 위해 각 point는 법선 및 접선(마찰) 충격을 저장한다 (the normal and tangential(friction) impulses)
  • b2Manifold에 저장된 데이터는 내부 사용에 최적화되어있다.
    • 이 데이터가 필요한 경우 일반적으로 b2WorldManifold 를 사용하여 접촉 법선(contact normal) 과 points를 생성하는것이 가장 좋다.
    • b2Manifold와 shape transforms과 반경(radii)을 제공할 필요가 있다.
1
2
3
4
5
6
7
8
9
b2WorldManifold worldManifold;
worldManifold.Initialize(&manifold, transformA, shapeA.m_radius,
transformB, shapeB.m_radius);

for (int32 i = 0; i \< manifold.pointCount; ++i)
{
    b2Vec2 point = worldManifold.points[i];
    ...
}
  • world manifold는 original manifold의 point count를 사용한다.
  • 시뮬레이션 중에 shape가 이동하고 manifolds가 변경되어질 수 있다. (points이 추가되거나 제거)
  • b2GetPointStates를 사용하여 이를 감지할 수 있다.
1
2
3
4
5
6
7
b2PointState state1[2], state2[2];
b2GetPointStates(state1, state2, &manifold1, &manifold2);

if (state1[0] == b2_removeState)
{
    // process event
}

4.3.3. Distance

  • b2Distance 함수는 두 shape 사이의 거리를 계산하는데 사용할 수 있다.
  • distance function은 b2DistanceProxy로 변환하려면 두 shape 모두 필요하다.
    • 반복호출에 대해 distance function을 warm start하는데 사용되는 일부 캐싱도 있다.

4.3.4. Time of impact

  • 만일 두 shape가 빠르게 움직이는 경우 단일 time step에서 서로를 터널링할 수 있다.
  • b2TimeOfImpact 함수는 두 개의 움직이는 shape가 충돌하는 시간을 결정하는 데 사용된다.
    • 이를 TOI(time of impact)라고 한다.
    • 이것의 목적은 터널 방지이다.
    • 특히, 움직이는 물체가 static level geometry 외부로 터널링되는 것을 방지하도록 설계되었다.
    • 이 함수는 두 shape의 회전 및 변환에 대해 처리하지만 회전이 충분히 크면 함수가 충돌을 놓칠 수 있다.
    • 하지만, 이 함수는 여전히 non-overlapped time을 보고하고 모든 translational collisions을 잡아낸다.
  • time of impact 함수는 초기 분리 축(initial separating axis)을 식별하고, shape가 해당 축에서 교차하지 않도록한다.
    • 이것은 최종 위치에서 명확한 충돌을 놓칠 수 있다.
    • 이 접근 방식은 일부 충돌을 놓칠 수 있지만 매우 빠르고 터널 방지에 적합하다.
  • 회전 크기에 제한을 두는 것은 어려우며, 작은 회전에 대해 충돌이 누락되는 경우가 있다.

    • 일반적으로 이러한 놓친 충돌은 게임 플레이에 해를 끼치지 않아야한다.(충돌하는 경향이 있음.)
  • 이 함수는 두개의 shape(b2DistanceProxy로 변환된) 과 두 개의 b2Sweep 구조가 필요하다.
    • sweep structure는 shape의 초기 및 최종 변환을 정의한다.
  • 만일 고정 회전(fixed rotations)을 사용하면, shape cast를 수행할 수 있다.
    • 이 경우, the time of impact function은 모든 충돌을 놓치지 않는다.

4.4. Dynamic Tree

  • b2DynamicTree는 많은 shape를 효율적으로 구성하는데 사용된다.

  • 이 클래서는 어떤 shape인지 모르지만, 사용자 데이터 포인터가 있는 AABBs(axis-aligned bounding boxes)에서 작동한다.

  • dynamic tree는 계층적 AABB 트리이다.

    • 트리의 각 내부 노드에는 두개의 자식을 가진다.
    • 리프노드는 단일 사용자 AABB(single user AABB)이다.
    • 트리는 퇴화 입력(degenerate input)의 경우에도 균형을 유지하기 위해 회전을 사용한다.
  • 트리 구조는 효율적인 ray casts 그리고 영역 쿼리를 허용한다.

    • 예를 들어, scene에 여러 shape가 있을 수 있다. 여기서 ray casst를 수행하려면, brute force 방법으로 각 shape에 대해 ray cast 하는 방법을 사용할 수 있다.
    • 이런 방법은 비효율적이다. (shape가 서로 떨어져 있을 경우)
    • 그러므로 dynamic tree를 유지하고, 이 tree에 대해 raycast를 수행하여 많은 개수의 shape를 스킵할 수 있다.
  • region query: tree를 사용하여 쿼리 AABB와 겹치는 모든 리프 AABB를 찾음.

    • 많은 shape를 스킵할 수 있기 때문에 빠르다.
  • 일반적으로 이 트리를 직접 사용하지 않는다.

    • b2World 클래스를 사용하여 레이 캐스트 및 지역 쿼리를 수행한다.

4.5. Broad-phase

  • 물리 단계에서 충돌처리는 narrow-phase와 broad-phase로 나눌 수 있다.
  • narrow-phase: shape 쌍에서 접점을 계산
    • N개의 shape: N*N/2 개의 쌍에 대해 narrow-phase를 수행해야한다.
  • b2BroadPhase 클래스는 이러한 쌍을 관리하기 위해 dynamic tree를 사용하여 부하를 줄인다.
    • 이것은 narrow-phase 호출의 수를 크게 줄여준다.
  • broad-phase: 일반적으로 직접 상호작용하지 않는다.(내부적으로 box2D가 생성하고 관리)
    • b2BroadPhase는 Box2D의 시뮬레이션 루프에 맞게 설계되었다.

5. Dynamics Module

  • 다이나믹 모듈은 Box2D의 가장 복잡한 부분이다.
  • 대부분 사용자가 상호작용하는 모듈.
  • Common과 Collision 모듈 위에 있는 모듈
  • 아래와 같은 클래스를 가지고 있다.
    • fixture
    • rigid body
    • contact
    • joint
    • world
    • listener
  • 클래스 사이에 많은 종속성이 있다. (다른 클래스를 참조)

5.1. Bodies

  • position 과 velocity를 가지고 있다.
  • forces, torques, impulses (힘, 토크, 충격)을 bodies에 적용할 수 있다.
  • Body의 종류: static, kinematic, dynamic
  • body는 fixtures(shapes)의 척추
  • bodies는 fixture를 운반하면서 world에서 움직인다.
  • Body는 항상 강체이다.
    • 즉, 동일한 강체에 부착된 fixtures는 서로에 대해 상대적으로 움직이지 않고, 서로 충돌하지 않는다.
  • fixtures에는 collision geometry 와 밀도(density)가 있다.
  • 일반적으로 body는 fixtures에서 mass 속성을 얻는다.
    • 그러나 body를 만든 후 mass 속성을 override할 수 있다.
  • 일반적으로 생성한 모든 bodies에 대해 pointers을 유지한다.
    • 그러므로 body position을 쿼리하여 그래픽 개체의 위치를 업데이트할 수 있다.
    • 또한 포인터에 대한 작업이 완료되면 파괴할 수 있다.

5.1.1. b2_staticBody

  • 시뮬레이션에서 움직이지 않음
  • 무한한 mass를 가진것 처럼 동작
  • mass와 inverse mass값 == 0
  • 사용자가 수동적으로 이동 가능
  • 속도 == 0
  • 다른 static, kinematic body와 충돌하지 않는다.

5.1.2. b2_kinematicBody

  • 속도에 따라 시뮬레이션 중에 움직임
  • forces에 반응하지 않음
  • 수동적으로 움직일 수 있지만 일반적으로 속도를 설정하여 이동시킴
  • 무한한 mass를 가진것처럼 동작하지만
  • mass와 inverse mass 값 == 0
  • 다른 static, kinematic과 충돌하지 않는다.

5.1.3. b2_dynamicBody

  • 완전히 시뮬레이션되는 body
  • 사용자가 수동으로 이동할 수 있지만, 일반적으로 forces에 따라 움직임
  • 어떠한 body와도 충돌 가능하다.
  • 0이 아닌 질량을갖는다
  • 만약 0으로 설정하면 자동적으로 1kg의 질량을 갖고 회전하지 않는다.

5.1.4. Body Definition

  • body를 생성하기 전에 body definition을 생성해야한다. (b2BodyDef)
  • 초기화 하는데 필요한 데이터를 가진다.
  • 이 정의에서 body로 데이터를 복사한다.
    • 정의에 대한 포인터는 유지하지 않는다.
    • 정의를 재활용하여 여러 body를 생성할 수 있다.

5.1.4.1. Body Type

  • static, kinematic, and dynamic.
  • 나중에 이를 변경하는것인 비용이 비싸므로 생성시에 설정
  • 필수적이다.
1
2
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;

5.1.4.2. Position and Angle

  • 생성시 body위치를 초기화
    • world 원점에서 생성하고, body를 움직이는것보다 성능이 좋다.
    • 원점에서 여러 body를 생성하면 성능이 저하된다.
  • body’s origin: Fixtures 그리고 joints은 body 원점을 기준으로 부착된다.
  • 질량 중심 위치(cenger of mass): 부착된 shape의 질량분포(mass distribution)에서 결정되거나 b2MassData로 명시적으로 설정할 수 있다.
    • Box2D의 내부 계산의 대부분은 질량 중심 위치를 사용한다.
    • 예를들어, b2Body는 질량중심에 대한 선형속도를 저장한다.
  • body의 각도를 라디안 단위로 지정할 수 있으며, 이는 질량중심위치의 영향을 받지 않는다.
  • 나중에 mass 속성들을 변경하면, body의 질량중심이 이동할 수 있지만, 원점 위치는 변경되지 않고, 부착된 shapes 과 joints 또한 이동하지 않는다.
1
2
3
b2BodyDef bodyDef;
bodyDef.position.Set(0.0f, 2.0f); // the body's origin position.
bodyDef.angle = 0.25f * b2_pi; // the body's angle in radians.
  • rigid body는 참조 프레임이다.
    • 해당 프레임에서 fixtures 및 joints를 정의할 수 있다.
    • 이것들은 local 프레임에서 절대 움직이지 않는다.

5.1.4.3. Damping

  • bodies의 world 속도를 줄이기 위해 사용된다.
  • friction은 접촉할 때 발생함(damping과 다름)
    • 마찰을 대체 할 수 없음, 두가지 모두 사용해야함.
  • Damping 매개변수는 0~ INF 사이
    • 0: 감쇠가 없음
    • INF: 전체 감쇠
  • 일반적으로 0 과 0.1 사이의 값을 사용한다.
1
2
3
b2BodyDef bodyDef;
bodyDef.linearDamping = 0.0f;
bodyDef.angularDamping = 0.01f;
  • Damping은 안정성과 성능에 대한 근사치
    • 작은 감쇠값: timestep 과 무관
    • 더 큰 감쇠값: timestep 에 따라 효과가 달라짐

5.1.4.4. Gravity Scale

  • 단일 body의 중력을 조절 (증가하면 안정성이 떨어짐)
1
2
3
// Set the gravity scale to zero so this body will float
b2BodyDef bodyDef;
bodyDef.gravityScale = 0.0f;

5.1.4.5. Sleep Parameters

  • 시뮬레이션 횟수가 적을 수록 좋기 때문에 body를 중단하게 함.
  • Box2D가 body(or group)이 정지했다고 판단하면 body는 CPU 오버헤드가 거의 없는 절전상태에 들어간다.
  • awake 상태의 body와 충돌하면 깨어나게된다.
  • Bodies에 연결된 joint 나 접점이 파괴된 경우에도 body가 깨어난다.
  • 수동으로 깨울 수 있다.
  • Body 정의에서 잠을 잘 수 있는지 와 생성할 때의 상태를 지정할 수 있다.
1
2
3
b2BodyDef bodyDef;
bodyDef.allowSleep = true;
bodyDef.awake = true;

5.1.4.6. Fixed Rotation

  • 캐릭터와 같은 강체가 회전하지 않게 하기 위한 설정
  • 하중이 가해져도 회전하지 않는다.
1
2
b2BodyDef bodyDef;
bodyDef.fixedRotation = true;
  • 이 플래그는 회전 관성(rotational inertia) 그리고 그 역(inverse)이 0으로 설정되도록 한다.

5.1.4.7. Bullets

  • 이산 시뮬레이션에서 강체는 한 timestap에서 더 많이 이동할 수 있다.
  • 터널링에 관한 것
    • 기본적으로 Box2D는 연속 충돌 감지(CCD)를 사용하여 dynamic이 static을 터널링하는 것을 방지한다.
    • 이전 위치에서 새 위치로 shape는 sweeping하여 수행한다.
    • 엔진은 sweeping동안 새로운 충돌을 찾고, 충돌시간(TOI)을 계산한다.
    • body가 첫번째 TOI로 이동된 다음, solver가 하위 단계를 수행하여 전체 timestep을 완료한다.(하위 단계 내에서 추가 TOI이벤트가 있을 수 있다.)
  • 일반적으로 ccd는 dynamic에서 사용되지 않는다.
    • 이는 성능을 합리적으로 유지하기 위해 수행된다.
    • 일부 게임에서 dynamic body에 ccd가 필요할 경우가 있다. (ex. 고속 총알을 dynamic body에, 이 경우 ccd가 없으면 뚫릴 수 있음)
  • 빠르게 움직이는 객체는 bullets이라는 라벨을 붙일 수 있다.
    • bullet은 static, dynamic body 모두에 ccd를 수행한다.
1
2
b2BodyDef bodyDef;
bodyDef.bullet = true;
  • 이 플래그는 dynamic body일 경우에만 유효하다.

5.1.4.8. Activation

  • 충돌이나 역학(collision or dynamics)에 참여하지 않을 수 있다.
    • 이 상태는 body가 다른 body에 의해 깨어나지 않고, body의 fixture가 broad-phase에 배치되지 않는다는 점을 제외하고는 sleep과 유사하다.
  • 즉, 충돌, 레이 캐스트에 참여하지 않는다.
  • 이 설정은 나중에 변경할 수 있다.
1
2
b2BodyDef bodyDef;
bodyDef.active = true;
  • joint는 비활성 body에 연결될 수 있지만, 시뮬레이션되지 않는다.
    • body를 활성화 할 때 joint가 왜곡되지 않도록 주의해야한다.
  • body를 활성화하는 것은 body를 처음부터 생성하는 것 만큼 비용이 많이 든다.
    • 따라서 streaming worlds에서 이를 활용하면 안된다.
    • 이 경우 생성/ 파괴를 사용하여 메모리를 절약해야한다.

5.1.4.9. User Data

  • 사용자 데이터는 void pointer이다.
  • 응용 프로그램 객체를 연결할 수 있는 연결고리이다,
  • 일관성있게 객체 타입을 연결해야한다.
1
2
b2BodyDef bodyDef;
bodyDef.userData.pointer = reinterpret_cast<uintptr_t>(&myActor);

5.1.5. Body Factory

  • body는 world class에서 제공하는 body factory를 사용하여 생성 및 파괴된다.
  • 이를 통해 world는 효율적으로 메모리 관리를 할 수 있다.
1
2
3
4
5
6
7
b2World* myWorld;
b2Body* dynamicBody = myWorld->CreateBody(&bodyDef);

// ... do stuff ...

myWorld->DestroyBody(dynamicBody);
dynamicBody = nullptr;

주의: body를 생성할 때 new, malloc을 사용해서는 안된다.

  • box2D는 body definition 또는 그 데이터에 대한 참조를 유지하지 않는다. (사용자 데이터 포인터는 유지)
  • 따라서 임시 body 정의를 생성하고, 동일한 body 정의를 재사용할 수 있다.

  • body를 삭제하면 부착된 fixtures, joints는 자동적으로 삭제된다.

5.1.6. Using a Body

  • 생성한 후에, 많은 작업을 수행할 수 있다.
  • 질량 속성 설정, 위치 및 속도 접근, forces 적용, 점 및 벡터 변환 등

5.1.7. Mass Data

  • 물체에는 질량(스칼라), 질량 중심(2-vector), 회전 관성(스칼라) 가 있다.
  • static body의 경우 질량 및 회전 관성 == 0
  • 물체의 회전이 고정되어 있으면 회전 관성 == 0

  • 일반적으로 body에 fixture를 추가할 때 body의 mass 속성이 자동으로 설정된다.
  • 런타임에 body의 질량을 조정할 수 있다.
1
void b2Body::SetMassData(const b2MassData* data);
  • body의 mass를 직접 설정한 후 fixture에 의해 지시된 natural mass로 되돌릴 경우 아래와 같은 함수를 사용한다.
1
void b2Body::ResetMassData();
  • body의 mass data와 관련된 함수는 다음과 같다.
1
2
3
4
float b2Body::GetMass() const;
float b2Body::GetInertia() const;
const b2Vec2& b2Body::GetLocalCenter() const;
void b2Body::GetMassData(b2MassData* data) const;

5.1.8. State Information

  • body state 접근하는 함수들
1
2
3
4
5
6
7
8
9
10
11
12
void b2Body::SetType(b2BodyType type);
b2BodyType b2Body::GetType();
void b2Body::SetBullet(bool flag);
bool b2Body::IsBullet() const;
void b2Body::SetSleepingAllowed(bool flag);
bool b2Body::IsSleepingAllowed() const;
void b2Body::SetAwake(bool flag);
bool b2Body::IsAwake() const;
void b2Body::SetEnabled(bool flag);
bool b2Body::IsEnabled() const;
void b2Body::SetFixedRotation(bool flag);
bool b2Body::IsFixedRotation() const;

5.1.9. Position and Velocity

  • 속도와 위치에 접근하는 함수들
  • 이것은 렌더링할 때 일반적이다.
  • 위치를 설정할 수 있지만, 시뮬레이션 결과를 얻는것이 일반적
1
2
3
4
bool b2Body::SetTransform(const b2Vec2& position, float angle);
const b2Transform& b2Body::GetTransform() const;
const b2Vec2& b2Body::GetPosition() const;
float b2Body::GetAngle() const;
  • Local 그리고 world 좌표에서 질량 중심 위치에 접근할 수 있다.
  • 내부 시뮬레이션은 대부분 질량 중심을 사용한다. (보통 사용자가 접근하지 않는다.)
  • 일반적으로 body transform 을 사용할 수 있다.
    • ex. body가 정사각형일 경우 질량중심은 정사각형 중심에 있다. (body의 원점은 모서리일 수 있다.)
1
2
const b2Vec2& b2Body::GetWorldCenter() const;
const b2Vec2& b2Body::GetLocalCenter() const;
  • 선형 및 각속도에 접근할 수 있다.
  • 선형 속도(linear velocity): 질량 중심에 대한것, 질량 특성이 변경되면 변경될 수 있다.

5.1.10. Fiorces and Impulses

  • 힘, 토크, 충격을 body에 적용
  • 이 경우 하중이 가해지는 world point 를 넘겨줘야한다.
  • 이로 인해 질량 중심에 대한 토크가 발생한다.
1
2
3
4
void b2Body::ApplyForce(const b2Vec2& force, const b2Vec2& point);
void b2Body::ApplyTorque(float torque);
void b2Body::ApplyLinearImpulse(const b2Vec2& impulse, const b2Vec2& point);
void b2Body::ApplyAngularImpulse(float impulse);
  • 힘, 토크, 충격을 가하면 body가 깨어나게 된다. (For example, you may be applying a steady force and want to allow the body to sleep to improve performance.)
  • 이를 막기 위해 아래와 같은 코드를 추가할 수 있다.
1
2
3
4
if (myBody->IsAwake() == true)
{
    myBody->ApplyForce(myForce, myPoint);
}

5.1.11. Coordinate Transformations

  • body class에는 local 과 world space사이에서 점과 벡터를 변환하는 데 도움이 되는 몇가지 유틸리티 함수가 있다.
    • 책추천: “Essential Mathematics for Games and Interactive Applications” by Jim Van Vert
  • 이러한 함수들은 inline일 경우 효율적이다.
1
2
3
4
b2Vec2 b2Body::GetWorldPoint(const b2Vec2& localPoint);
b2Vec2 b2Body::GetWorldVector(const b2Vec2& localVector);
b2Vec2 b2Body::GetLocalPoint(const b2Vec2& worldPoint);
b2Vec2 b2Body::GetLocalVector(const b2Vec2& worldVector);

5.1.12. Acessing Fixtures, Joints, and Contacts

  • body의 fixtures에 접근할 수 있다. (반복문을 통해)
1
2
3
4
5
for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext())
{
    MyFixtureData* data = (MyFixtureData*)f->GetUserData();
    // do something with data ...
}
  • body의 joint 리스트 또한 유사하다.
  • 연관된 contacts 리스트도 제공한다.
    • 이를 사용해 현재 접촉에 대한 정보를 얻을 수 있다.
    • 이 리스트에는 이전 timestep 동안 존재했던것들이 없을 수 있다.

5.2. Fixtures

  • shape는 body에 대해 알지 못한다.
    • 물리 시뮬레이션과 독립적으로 사용될 수 있다.
  • 따라서 body에 shape를 붙이기 위해 b2Fixture 클래스를 사용한다.
    • 여러 fixture가 있는것을 compound body라고 한다.
  • fixture는 아래와 같은 데이터를 가지고 있다.
    • a single shape
    • broad-phase proxies
    • density, friction, and restitution
    • collision filtering flags
    • back pointer to the parent body
    • user data
    • sensor flag

5.2.1. Fixture Creation

  • fixture 정의를 생성하고, body에서 생성한다.
  • 부모 body가 제거되면, 자동으로 fixture도 제거된다. (따로 포인터로 저장할 필요 없다.)
1
2
3
4
5
b2Body* myBody;
b2FixtureDef fixtureDef;
fixtureDef.shape = &myShape;
fixtureDef.density = 1.0f;
b2Fixture* myFixture = myBody->CreateFixture(&fixtureDef);
  • 깨지기 쉬운 물체를 모델링하기 위해 fixture를 body에서 제거할 수 있다.
1
myBody->DestroyFixture(myFixture);

5.2.2. Density

  • fixture 밀도는 mass의 속성을 계산하는데 사용된다.
  • 밀도는 0 또는 양수이다.
  • 일반적으로 모든 fixture에 대해 유사한 밀도를 사용해야한다. (stacking 안정성 향상)

  • 밀도를 설정할 때 body의 질량은 조정되지 않는다. (ResetMassData 호출해야한다.)
1
2
3
4
b2Fixture* fixture;
fixture->SetDensity(5.0f);
b2Body* body;
body->ResetMassData();

5.2.3. Friction

  • friction은 물체가 사실적으로 서로를 따라 미끄러지도록 하는 데 사용된다.
  • Box2D는 static, dynamic friction을 지원하지만 둘다 동일한 매개변수를 사용한다.
  • 마찰은 Box2D에서 정확하게 시뮬레이션된다.
  • 마찰 강도는 수직력(normal force)에 비례한다.(쿨롱 마찰)
  • 마찰 매개변수는 일반적으로 0과 1 사이에서 설정되지만, 음수가 아닌 값일 수 있다.
    • 마찰 값이 0이면 마찰이 꺼짐
    • 1이면 강해짐
    • 두 shape 사이에서 마찰이 계산될 때 Box2D는 두 상위 fixture의 마찰 매개변수를 결합한다.(기하평균)
    • 따라서 하나가 마찰이 0일 경우 접점은 마찰이 없다.
1
2
3
4
b2Fixture* fixtureA;
b2Fixture* fixtureB;
float friction;
friction = sqrtf(fixtureA->friction * fixtureB->friction);
  • b2Contact::SetFriction 을 사용하여 혼합된 마찰을 override할 수 있다.
    • b2ContactListener callback 에서 수행된다.

5.2.4. Restitution

  • 복원은 물체를 튕기기 위해 사용된다.
  • 복원 값은 0~1 사이로 설정된다.
  • 테이블에 공을 떨어뜨릴 때 만약 0의 값을 가지고 있으면 공은 튀지 않는다.
    • 이를 비탄성 충돌이라고 부른다.
  • 값이 1 이면 공의 속도가 정확히 반영된다.
    • 이를 완전 탄성 충돌이라고 부른다.
  • 아래의 코드로 결합될 수 있다.
1
2
3
4
b2Fixture* fixtureA;
b2Fixture* fixtureB;
float restitution;
restitution = b2Max(fixtureA->restitution, fixtureB->restitution);
  • 이와 같은 결합으로 bouncy floor가 아니더라도 bouncy ball을 만들 수 있다.

  • b2Contact::SetRestitution을 사용하여 기본 혼합을 override할 수 있다.
    • b2ContactListener callback 에서 수행된다.
  • 다중 접촉에 대해서 복원은 대략적인 시뮬레이션을 수행한다.
    • Box2D가 반복 솔버를 사용하기 때문.
    • Box2D는 충돌 속도가 작은 경우에도 비탄성충돌을 사용한다. (jitter를 방지하는데 사용 b2_velocityRhreshold)

5.2.5. Filtering

  • collision filtering을 사용하면 fixtures 사이의 충돌을 방지할 수 있다.
    • 예를들어, 자전거를 타는 캐릭터, 자전거가 지형과 충돌, 캐릭터가 지형과 충돌하기를 원하지만 캐릭터와 자전거가 충돌하지 않게(겹쳐져야하므로)
  • 범주 및 그룹을 사용하여 이러한 충돌 필터링을 지원한다.
  • Box2D는 16개의 충돌 범주를 지원한다.
    • 각 fixture에 대해 카테고리를 지정할 수 있다.
    • 또한 이 fixture가 충돌할 수 있는 다른 카테고리를 지정할 수 있다.
    • 예를 들어, 멀티플레이어 게임에서 모든 플레이어는 서로 충돌하지 않고 몬스터는 서로 충돌하지 않지만, 플레이어와 몬스터는 충돌하게 지정 가능하다.
  • 이것은 비트 마스킹으로 수행된다.
1
2
3
4
5
b2FixtureDef playerFixtureDef, monsterFixtureDef;
playerFixtureDef.filter.categoryBits = 0x0002;
monsterFixtureDef.filter.categoryBits = 0x0004;
playerFixtureDef.filter.maskBits = 0x0004;
monsterFixtureDef.filter.maskBits = 0x0002;
  • 충돌이 발생하는 규칙은 다음과 같다.
1
2
3
4
5
6
7
8
9
uint16 catA = fixtureA.filter.categoryBits;
uint16 maskA = fixtureA.filter.maskBits;
uint16 catB = fixtureB.filter.categoryBits;
uint16 maskB = fixtureB.filter.maskBits;

if ((catA & maskB) != 0 && (catB & maskA) != 0)
{
    // fixtures can collide
}
  • 충돌 그룹을 사용하면 통합 그룹 인덱스를 지정할 수 있다.
    • 동일한 그룹 인덱스를 가진 모든 fixture가 항상 충돌(양수 인덱스)하거나 충돌하지 않도록(음수 인덱스) 할 수 있다.
    • 이것은 일반적으로 자전거 부품과 같이 관련성이 있는 항목에 사용된다.
    • 아래의 예에서 fixture1 과 fixture2는 항상 충돌하지만 fixture3과 4는 절대 충돌하지 않는다.
1
2
3
4
fixture1Def.filter.groupIndex = 2;
fixture2Def.filter.groupIndex = 2;
fixture3Def.filter.groupIndex = -8;
fixture4Def.filter.groupIndex = -8;
  • 다른 그룹 인덱스의 fixture간의 충돌은 카테고리 및 마스크 비트에 따라 필터링된다.

    • 즉, 그룹 필터링은 카테고리 필터링보다 우선순위가 높다.
  • 추가적으로 알아야하는 필터링은 다음과 같다.
    • static body에 붙은 fixture는 오직 dynamic 과 충돌
    • kinematic body에 붙은 fixture는 오직 dynamic 과 충돌
    • 같은 body에 붙은 fixtures는 결코 서로 충돌하지 않는다.
    • 추가적으로 joint로 연결된 body의 fixture간의 충돌을 선택적으로 활성화/비활성화 할 수 있다.
  • 때때로 fixture가 이미 생성된 후에 filtering을 변경해야할 수 있다.
    • GetFilterData, SetFilterData를 사용하여 b2Filter 구조를 가져오고 설정할 수 있다.
    • 필터 데이터를 변경해도 다음 time step까지 접촉점(contacts)이 추가되거나 제거되지 않는다. (world class)

5.2.6. Sensors

  • 두개의 fixture가 겹치지만 충돌 응답이 없어야 할 때.
  • 센서는 충돌을 감지하지만 응답을 생성하지 않는 fixture이다.
  • 모든 fixture를 센서로 표시할 수 있다.
    • static, kinematic, dynamic
  • 센서와 solid fixture를 혼합하여 사용할 수 있다.
  • 센서는 최소한 하나의 body가 동적일 때만 접점을 형성(감지)한다.
    • 센서는 접점을 생성하지 않는다.
  • 두가지 방법으로 센서 상태를 가져올 수 있다.
    1. b2Contact::IsTouching
    2. b2ContactListener::BeginContact and b2ContactListener::EndContact

5.3. Joints

Joint Definition Joint Factory Using Joints Distance Joint Revolute Joint Prismatic Joint Pulley Joint Gear Joint Mouse Joint Wheel Joint Weld Joint Rope Joint Friction Joint Motor Joint Wheel Joint

5.4. Contacts

Contact Class Accessing Contacts


Contact Listener shape rigid body fixture constraint contact constraint joint joint limit joint motor world solver continuous collision b2_staticBody b2_kinematicBody


b2_dynamicBody contact point contact normal contact separation contact manifold normal impulse tangent impulse contact ids


Begin Contact Event End Contact Event Pre-Solve Event Post-Solve Event


Contact Filtering

5.5. World

  • b2World 클래스에는 bodies 그리고 joints이 포함된다.
  • 이것은 시뮬레이션의 모든 측면을 관리하고, 비동기 쿼리를 할 수 있게해준다. (AABB쿼리, 레이캐스트)
  • Box2D와의 대부분의 상호 작용은 b2World 객체와 함께 수행된다.

5.5.1. Creating and Destroying a World

  • 중력 벡터와 body가 sleep 상태로 변할 수 있는지에 대한 여부만 넘겨주면 된다.
  • 일반적으로 포인터를 사용하여 생성, 해제 한다.
1
2
3
4
5
b2World* myWorld = new b2World(gravity, doSleep);

// ... do stuff ...

delete myWorld;

5.5.2. Using a World

  • world 클래스에는 body와 조인트를 생성하고 파괴하는 팩토리가 있다.
  • 그 외에 여러 상호작용을 지원한다.

5.5.2.1. Simulation

  • world 클래스는 시뮬레이션을 구동하는데 사용된다.
  • time step 과 velocity, position iteration count 를 지정해야한다.
1
2
3
4
float timeStep = 1.0f / 60.f;
int32 velocityIterations = 10;
int32 positionIterations = 8;
myWorld->Step(timeStep, velocityIterations, positionIterations);
  • time step 후에 body와 joints에 대한 검사를 수행할 수 있다.
  • actor를 업데이트하고 렌더링할 수 있도록 body에서 position을 가져올 수 있다.
  • 게임 루프 어느 곳에서든 timestep을 수행할 수 있다.
    • 예를들어, 해당 프레임의 새로운 body에 대한 충돌 결과를 얻으려면 time step이전에 body를 생성해야한다.
    • 고정된 timestep을 사용해야한다. (더 큰 time step은 low frame rate 시나리오에서 성능을 높일 수 있다.)
    • 일반적으로 1/30 보다 작은 time step을 사용한다. (1/60은 고품질 시뮬레이션)
  • 반복횟수는 제약 조건 솔버가 world의 모든 contacts 과 joints을 sweep하는 횟수를 제어한다.
    • 더 많은 반복은 더 나은 시뮬레이션을 생성한다.
    • (하지만 60hz에서 10번 반복이 30hz에서 20회 반복보다 더 좋다)
  • stepping 후에는 body에 적용하고 있는 모든 forces를 제거해야한다.
    • clearforces 함수를 통해 수행할 수 있다.
1
myWorld->ClearForces();

5.5.2.2. Exploring the World

  • body, contact, joint 리스트들을 가져와 탐색할 수 있다.
  • 아래의 코드는 모든 body를 깨우는 코드이다.
1
2
3
4
for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    b->SetAwake(true);
}
  • 실제 코드에 적용하려면 좀 더 복잡해진다. 예를들어 아래는 오류가 발생할 수 있다.
1
2
3
4
5
6
7
8
for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    GameActor* myActor = (GameActor*)b->GetUserData().pointer;
    if (myActor->IsDead())
    {
        myWorld->DestroyBody(b); // ERROR: now GetNext returns garbage.
    }
}
  • 만약 위처럼 조회하던 중에 body가 파괴되면(죽은 캐릭터를 파괴), next 포인터는 쓰레기값을 리턴한다.
  • 이를 해결하기 위해서는 body를 파괴하기전에 next 포인터를 복사하는것이다.
1
2
3
4
5
6
7
8
9
10
11
12
b2Body* node = myWorld->GetBodyList();
while (node)
{
    b2Body* b = node;
    node = node->GetNext();

    GameActor* myActor = (GameActor*)b->GetUserData().pointer;
    if (myActor->IsDead())
    {
        myWorld->DestroyBody(b);
    }
}
  • 위 코드는 현재의 body를 안전하게 제거하지만, 만약 여러 body를 제거하려면 다음과 같이 코드를 짜야한다. (GameCrazyBodyDestroyer에서 body가 삭제되면, 다시 world 쿼리)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
b2Body* node = myWorld->GetBodyList();
while (node)
{
    b2Body* b = node;
    node = node->GetNext();

    GameActor* myActor = (GameActor*)b->GetUserData().pointer;
    if (myActor->IsDead())
    {
        bool otherBodiesDestroyed = GameCrazyBodyDestroyer(b);
        if (otherBodiesDestroyed)
        {
            node = myWorld->GetBodyList();
        }
    }
}

5.5.2.3. AABB Queries

  • 경우에 따라 영역에 있는 모든 shape를 확인하고 싶을 때가 있다.
    • b2World 클래스는 이를 위해 광범위한 데이터 구조를 사용해 log(N)방법을 사용한다.
  • 쿼리하기 위해서 world 좌표를 가진 AABB와 b2QueryCallback을 구현해야한다.
  • 예를 들어 다음 코드는 지정된 AABB와 겹치는 모든 fixture 를 찾고 관련된 모든 body를 깨운다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyQueryCallback : public b2QueryCallback
{
public:
    bool ReportFixture(b2Fixture* fixture)
    {
        b2Body* body = fixture->GetBody();
        body->SetAwake(true);

        // Return true to continue the query.
        return true;
    }
};

// Elsewhere ...
MyQueryCallback callback;
b2AABB aabb;

aabb.lowerBound.Set(-1.0f, -1.0f);
aabb.upperBound.Set(1.0f, 1.0f);
myWorld->Query(&callback, aabb);
  • You cannot make any assumptions about the order of the callbacks.

5.5.2.4. ?Ray Casts?

  • 레이 캐스트를 통해 가시선 검사(line-of-sight check), 총 발사(fire gun) 등을 수행할 수 있다.
  • 콜백 클래스를 구현하고, 시작점과 끝점을 제공하여 광선 투사를 수행해야한다.
  • world 클래스는 광선 투사를 수행한다.
  • world 클래스는 구현한 클래스에 광선에 맞은 fixture를 보고한다.
  • 콜백은 fixture, 교차점, 단위 법선 벡터 및 광선에 따른 fractional distance를 함께 제공한다.
    • 콜백의 순서에 대해 어떠한 가정도 할 수 없다.
  • 반환된 fraction을 통해 레이 캐스트의 연속을 제어한다.
    • fraction이 0일 경우, 레이 캐스트가 종료되어야함을 나타낸다.
    • 1일 경우 적중이 발생하지 않아, 계속되어야함을 나타낸다.
  • 만일 인수목록에서 fraction을 반환하면, 광선이 현재 교차점으로 잘린다.
    • 따라서 모든 shape를 레이 캐스팅하거나, 어느 한 shape를 레이캐스팅하거나, 적절한 fraction을 반환하여 가장 가까운 shape를 레이캐스팅할 수있다.
  • fixture를 필터링하기 위해 -1의 분수를 반환할 수 있다.
    • 이러면 fixture가 존재하지 않는 것처럼 레이 캐스트가 진행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// This class captures the closest hit shape.
class MyRayCastCallback : public b2RayCastCallback
{
public:
    MyRayCastCallback()
    {
        m_fixture = NULL;
    }

    float ReportFixture(b2Fixture* fixture, const b2Vec2& point,
                        const b2Vec2& normal, float fraction)
    {
        m_fixture = fixture;
        m_point = point;
        m_normal = normal;
        m_fraction = fraction;
        return fraction;
    }

    b2Fixture* m_fixture;
    b2Vec2 m_point;
    b2Vec2 m_normal;
    float m_fraction;
};

// Elsewhere ...
MyRayCastCallback callback;
b2Vec2 point1(-1.0f, 0.0f);
b2Vec2 point2(3.0f, 1.0f);
myWorld->RayCast(&callback, point1, point2);

Caution: Due to round-off errors, ray casts can sneak through small cracks between polygons in your static environment. If this is not acceptable in your application, trying slightly overlapping your polygons.

6. Loose Ends

6.1. User Data

6.2. Custom User Data

6.3. Implicit Destruction

6.4. Pixels and Coordinate Systems

6.5. Debug Drawing

6.6. Limitations

7. links

https://box2d.org/documentation/

https://self-toeic.tistory.com/entry/Box2D-220-%ED%95%9C%EA%B8%80%ED%99%94-Chapter1-Introduction-Box2D-v220-User-Manual

https://analog-green.tistory.com/511

http://kimilb412-2.blogspot.com/2014/02/

https://www.youtube.com/watch?v=IcVtjuwxlj4

https://www.youtube.com/watch?v=kEClRFKyGkw&t=730s

http://www.iforce2d.net/b2dtut/introduction

This post is licensed under CC BY 4.0 by the author.

[게임 프로그래밍 패턴] Design Patterns Revisited: Observer

[note] mediapipe와 ipc