본문 바로가기
Bullet Physics

Bullet physics - dynamic world with libgdx

by ses jeon 2021. 3. 25.

Using the libGDX 3D physics Bullet wrapper - part2

 

Bullet is a Collision Detection and Rigid Body Dynamics Library. In this second part of this tutorial we will learn how to use the libGDX Bullet wrapper for Rigid Body Dynamics. This is the part where we simulate real world physics like applying forces, responding to collisions and so forth.<more style="margin: 0px; padding: 0px; box-sizing: border-box;"></more>

In the first part of this tutorial we've seen what the Bullet wrapper is and how we can use it for collision detection. I assume that you've read that prior to this. We'll use its code as the base for this part of the tutorial.

If you have a look at the source of the Bullet wrapper, you'll see that it consists of five parts, each with its own java package. These are:

Bullet 은 충돌 감지 및 리지드 바디 다이내믹 라이브러리입니다. 이 튜토리얼의 두 번째 부분에서는 Rigid Body Dynamics에 libGDX Bullet 래퍼를 사용하는 방법을 배웁니다. 이것은 우리가 힘을가하거나 충돌에 응답하는 것과 같은 실제 물리학을 시뮬레이션하는 부분입니다. <more></more>

이 튜토리얼의 첫 번째 부분 에서는 Bullet 래퍼가 무엇이며 어떻게 충돌 감지에 사용할 수 있는지 살펴 보았습니다. 나는 당신이 전에 이것을 읽었다 고 추측합니다. 자습서의이 부분에 대한 기본 코드로 코드를 사용합니다.

Bullet 래퍼의 소스 를 살펴보면 5 개의 부분으로 구성되며 각각은 자체 Java 패키지로 구성되어 있습니다. 이것들은:

  • linearmath
  • collision
  • dynamics
  • softbody
  • extras

The linearmath package contains some generic classes and methods, which are not directly related to physics. This is for example the btVector3 class and the bridge to the LibGDX Vector3 class. The collision package contains everything related to collision detection as we've seen before, like the collision shapes, collision world and near and broad phase classes. The dynamics package contains everything related to rigid body dynamics which we'll look at in this tutorial. The softbody package contains every related to soft-body and cloth simulation (in contrast to rigid bodies). And finally, the extra package contains some useful helper classes/tools, at the moment this only contains importing previously saved bullet world (e.g. from Blender).

linearmath 패키지에는 물리와 직접적인 관련이없는 몇 가지 일반적인 클래스와 메서드가 포함되어 있습니다. 예를 들어 btVector3 클래스와 LibGDX Vector3 클래스에 대한 브릿지입니다. collision 패키지에는 충돌 모양, 충돌 세계 및 가깝거나 넓은 위상 클래스와 같이 이전에 보았던 충돌 감지와 관련된 모든 것이 포함되어 있습니다. dynamics 패키지에는 강체 동역학과 관련된 모든 것이 포함되어 있습니다.이 강좌는이 자습서에서 살펴볼 것입니다. softbody 패키지에는 강체와는 달리 강체 및 천 시뮬레이션과 관련된 모든 요소가 포함되어 있습니다. 마지막으로, extra 패키지에는 유용한 헬퍼 클래스 / 툴이 포함되어 있습니다. 현재는 이전에 저장된 총알 월드 (예 : 블렌더) 만 가져올 수 있습니다.

 

Note that the order of these packages is important. The collision package uses classes from the linearmath package, but doesn't use classes from the dynamics package. Likewise the dynamics package relies on the collision package, but is independent of the softbody package. Or, in other words: dynamics is built on top of collision. When working with dynamics, you are still able to use the functionality of the collision package.

이 패키지의 순서는 중요합니다. collision 패키지는 linearmath 패키지의 클래스를 사용하지만 동적 패키지의 클래스는 사용하지 않습니다. 마찬가지로 동역학 패키지는 충돌 패키지에 의존하지만, 소프트 바디 패키지와는 독립적입니다. 즉, dynamics 은 collision 위에 구축됩니다. dynamics 작업 할 때 collision 패키지의 기능을 계속 사용할 수 있습니다.

Add dynamic properties

Before we can apply dynamics, we need to set some properties required for Bullet to perform some calculations. E.g. when you push against (apply a force to) an object, then the weight (mass) of the object is important on what happens. If the object is very heavyweight it might not move at all. But when it's lightweight, it might move quite some distance. Other properties, like the friction between the object and the surface it's on are also relevant. Because a btCollisionObject doesn't contain such properties, we need to use a subclass called btRigidBody. As the name suggests this class contains all properties of a rigid body.

역학을 적용하기 전에 Bullet에 필요한 일부 속성을 설정해야 일부 계산을 수행 할 수 있습니다. 예를 들어, 물체를 밀 때 (힘을 가하는 경우), 물체의 무게 (질량)는 무엇이 일어나는지에 중요합니다. 오브젝트가 매우 중량 인 경우 전혀 움직이지 않을 수 있습니다. 그러나 가볍기 때문에 꽤 먼 거리로 움직일 수 있습니다. 물체와 물체 사이의 마찰과 같은 다른 속성도 관련성이 있습니다. btCollisionObject 에는 이러한 속성이 없으므로 btRigidBody 라는 하위 클래스를 사용해야합니다. 이름에서 알 수 있듯이이 클래스에는 강체의 모든 속성이 포함되어 있습니다.

 

public class BulletTest implements ApplicationListener {
    ...
    static class GameObject extends ModelInstance implements Disposable {
        public final btRigidBody body;
        public boolean moving;

        public GameObject (Model model, String node, btRigidBody.btRigidBodyConstructionInfo constructionInfo) {
            super(model, node);
            body = new btRigidBody(constructionInfo);
        }

        @Override
        public void dispose () {
            body.dispose();
        }

        static class Constructor implements Disposable {
            public final Model model;
            public final String node;
            public final btCollisionShape shape;
            public final btRigidBody.btRigidBodyConstructionInfo constructionInfo;
            private static Vector3 localInertia = new Vector3();

            public Constructor (Model model, String node, btCollisionShape shape, float mass) {
                this.model = model;
                this.node = node;
                this.shape = shape;
                if (mass > 0f)
                    shape.calculateLocalInertia(mass, localInertia);
                else
                    localInertia.set(0, 0, 0);
                this.constructionInfo = new btRigidBody.btRigidBodyConstructionInfo(mass, null, shape, localInertia);
            }

            public GameObject construct () {
                return new GameObject(model, node, constructionInfo);
            }

            @Override
            public void dispose () {
                shape.dispose();
                constructionInfo.dispose();
            }
        }
    }
    ...
    @Override
    public void create () {
        ...
        constructors = new ArrayMap<String, GameObject.Constructor>(String.class, GameObject.Constructor.class);
        constructors.put("ground", new GameObject.Constructor(model, "ground", new btBoxShape(new Vector3(2.5f, 0.5f, 2.5f)), 0f));
        constructors.put("sphere", new GameObject.Constructor(model, "sphere", new btSphereShape(0.5f), 1f));
        constructors.put("box", new GameObject.Constructor(model, "box", new btBoxShape(new Vector3(0.5f, 0.5f, 0.5f)), 1f));
        constructors.put("cone", new GameObject.Constructor(model, "cone", new btConeShape(0.5f, 2f), 1f));
        constructors.put("capsule", new GameObject.Constructor(model, "capsule", new btCapsuleShape(.5f, 1f), 1f));
        constructors.put("cylinder", new GameObject.Constructor(model, "cylinder", new btCylinderShape(new Vector3(.5f, 1f, .5f)), 1f));
        ...
    }
}


View full source on github

 

Here we replaced the btCollisionObject of the GameObject class with the btRigidBody class, which extends btCollisionObject. To construct the body we now use a btRigidBodyConstructionInfo class. This class contains the btCollisionShape like we've used previously, but also contains other properties like the mass, friction, damping and others. The Constructor class now also contains this constructionInfo, this allows for a very convenient way to construct multiple instances of the same object, like we've seen before.

여기서는 GameObject 클래스의 btRigidBody 를 확장하는 btRigidBody 클래스로 대체했습니다. 본문을 구성하기 위해 이제 btRigidBodyConstructionInfo 클래스를 사용합니다. 이 클래스에는 이전에 사용한 것처럼 btCollisionShape 가 포함되어 있지만 mass , friction , damping 및 기타 속성도 포함되어 있습니다. Constructor 클래스에는이 constructionInfo 도 포함되어 있습니다. 이전에 보았 듯이 동일한 객체의 여러 인스턴스를 매우 편리하게 구성 할 수 있습니다.

 

When creating the btRigidBodyConstructionInfo, we need to specify the mass (the weight of the object), we use null for the second argument (we'll see soon why), next we supply the btCollisionShape as third argument and finally we need to specify the localInertia. localInertiais a static Vector3, so that it will be reused for each Constructor. If the mass is equal or less than zero, we simply set the local inertia also to zero. Otherwise we need to calculate the local intertia. Luckily the collision shape has a nice helper method to calculate it.

btRigidBodyConstructionInfo 만들 때 질량 (객체의 무게)을 지정해야하며 두 번째 인수에 null 을 사용합니다 (곧 이유는 알 수 있습니다). 그런 다음 세 번째 인수로 btCollisionShape 를 제공하고 마지막으로 localInertia . localInertia 는 정적 Vector3 이므로 각 Constructor 에 대해 다시 사용됩니다. 질량이 0보다 작거나 같으면 로컬 관성을 0으로 설정하기 만하면됩니다. 그렇지 않으면 우리는 지역의 intertia를 계산할 필요가있다. 운좋게도 충돌 셰이프는 그것을 계산할 수있는 좋은 도우미 메서드를 가지고 있습니다.

 

Note that the Constructor class still holds the btCollisionShape, even while the btRigidBodyConstructionInfo also contains this information. This is because we still own the shapeand need to dispose it when it's no longer needed. Of course the same goes for the constructionInfomember, so I added a line to the dispose method to dispose that as well.

btRigidBodyConstructionInfo 정보가 포함되어있는 경우에도 Constructor 클래스에는 btCollisionShape 가 유지됩니다. 이는 우리가 여전히 shape 소유 하고 더 이상 필요하지 않을 때 shape 을 dispose 해야하기 때문입니다. 물론 constructionInfo 멤버에 대해서도 마찬가지이므로 dispose 메서드에 선을 추가하여 처분합니다.

 

The constructors that we use for our test are mostly still the same, but we now have to include the mass when creating the Constructor. For now we'll just use a mass of 1f for each object, except for the ground, where we use a mass of 0f.

우리가 테스트에 사용하는 생성자는 대부분 여전히 동일하지만 Constructor 생성 할 때 대량을 포함해야합니다. 이제 우리는 단지 0f 의 질량을 사용하는 땅을 제외하고 각 대상에 대해 1f 의 질량을 사용합니다.

 

A zero mass isn't physically possible. It is used to indicate that the ground should not respond to any forces applied to it. It should always stay at the same location (and rotation), regardless of any forces or collisions that may apply to it. This is called a "static" object. The other objects (with a mass greater than zero) are called "dynamic" objects.

제로 질량은 물리적으로 가능하지 않습니다. 지면이지면에 가해지는 어떠한 힘에도 반응해서는 안된다는 것을 나타내는 데 사용됩니다. 그것은 적용 할 수있는 어떤 힘이나 충돌에 관계없이 항상 같은 위치 (그리고 회전)에 있어야합니다. 이를 "정적"개체라고합니다. 0보다 큰 질량을 가진 다른 객체를 "동적"객체라고합니다.

A small note about units

As you might know, most physics properties need to be specified using units. For example the mass is commonly specified in kilograms. The size of an object is specified in meters and time is specified in seconds. These are called SI units. It is advised to use them when possible. But sometimes it is just not practical to use them. For example when your objects are very huge or very small.

아시 다시 시겠지만 대부분의 물리 특성은 단위를 사용하여 지정해야합니다. 예를 들어 질량은 일반적으로 킬로그램 단위로 지정됩니다. 객체의 크기는 미터 단위로 지정되며 시간은 초 단위로 지정됩니다. 이것을 SI 단위 라고 부릅니다. 가능한 경우 사용하는 것이 좋습니다. 그러나 때때로 그것들을 사용하는 것은 실용적이지 않습니다. 예를 들어 당신의 물건이 매우 거대하거나 아주 작을 때.

 

Bullet performs best when values are around one (1f). There are a lot of factors (like floating point precision), but in practice it's best to keep the values of the properties of the the btRigidBody around one. So, if you are making a space game where the game objects are likely to be much larger than one meter, you might want to decide to use dekameters (10), hectometers (*100) or kilometers (*1000) instead. Or, if you are making a game where the objects are likely to be much smaller than one meter, you might want to decide to use decimeters (0.1), centimeters (*0.01) or millimeters (*0.001). Likewise, if your objects are likely to be much heavier or lighter than one kilogram, you might want to scale the mass also.

Bullet은 값이 1 ( 1f ) 근처 일 때 가장 잘 수행됩니다. 부동 소수점 정밀도와 같은 많은 요인이 있지만 실제로는 btRigidBody 의 속성 값을 유지하는 것이 가장 좋습니다. 따라서 게임 객체가 1 미터보다 훨씬 커질 수있는 우주 게임을 제작하는 경우, 대신 데크 미터 (10), 헥토 미터 (100) 또는 킬로미터 (1000) 를 사용하기로 결정할 수 있습니다 . 또는 오브젝트가 1 미터보다 훨씬 작을 가능성이있는 게임을 제작하는 경우, 십진법 ( 0.1), 센티미터 (0.01) 또는 밀리미터 (* 0.001) 를 사용하기로 결정할 수 있습니다 . 마찬가지로 물체가 1 킬로그램보다 훨씬 가볍거나 가벼운 경우 물체를 확장 할 수도 있습니다.

 

Scaling your units is generally no problem, as long as you keep consistent. So if you decide to use inches instead of meter, then you should also use inches per second for velocity, inches per second per second for acceleration (like gravity) and kilogram inches per second per second for forces. Read this article for more information.

일관성을 유지하는 한 대개 단위를 확장해도 아무런 문제가 없습니다. 그러므로 미터 대신 인치를 사용하기로 결정했다면, 속도는 초당 인치, 중력과 같은 초당 인치는 초당 인치, 힘은 초당 초당 킬로미터를 사용해야합니다. 자세한 내용이 기사 를 참조하십시오.

 

Keep in mind that in most cases the size of the (visual) model is the same as the size of the physics body and that the transformation matrix of the model instance can't contain a scaling component. Therefor the size of your model should match the desired units and any scaling applied should be "baked" prior to exporting the model from within your modeling application.

대부분의 경우 (시각적 인) 모델의 크기는 피직스 바디의 크기와 같으며 모델 인스턴스의 변형 행렬에는 스케일링 구성 요소가 포함될 수 없습니다. 따라서 모델의 크기는 원하는 단위와 일치해야하며 적용된 배율은 모델링 응용 프로그램에서 모델을 내보내기 전에 "구워야합니다".

Add the dynamics world

To actually apply dynamics, we need to add a dynamics world (btDynamicsWorld). This will replace (and extends) the btCollisionWorld that we've used before:

 

public class BulletTest implements ApplicationListener {
    ...
    btDynamicsWorld dynamicsWorld;
    btConstraintSolver constraintSolver;

    @Override
    public void create () {
        ...
        collisionConfig = new btDefaultCollisionConfiguration();
        dispatcher = new btCollisionDispatcher(collisionConfig);
        broadphase = new btDbvtBroadphase();
        constraintSolver = new btSequentialImpulseConstraintSolver();
        dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, constraintSolver, collisionConfig);
        dynamicsWorld.setGravity(new Vector3(0, -10f, 0));
        contactListener = new MyContactListener();

        instances = new Array<GameObject>();
        GameObject object = constructors.get("ground").construct();
        instances.add(object);
        dynamicsWorld.addRigidBody(object.body, GROUND_FLAG, ALL_FLAG);
    }

    public void spawn () {
        GameObject obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].construct();
        obj.transform.setFromEulerAngles(MathUtils.random(360f), MathUtils.random(360f), MathUtils.random(360f));
        obj.transform.trn(MathUtils.random(-2.5f, 2.5f), 9f, MathUtils.random(-2.5f, 2.5f));
        obj.body.setWorldTransform(obj.transform);
        obj.body.setUserValue(instances.size);
        obj.body.setCollisionFlags(obj.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
        instances.add(obj);
        dynamicsWorld.addRigidBody(obj.body, OBJECT_FLAG, GROUND_FLAG);
    }
    ...
    @Override
    public void dispose () {
        ...
        dynamicsWorld.dispose();
        constraintSolver.dispose();
        ...
    }
}


 

I've renamed the collisionWorld member to dynamicsWorld and made it a type of btDynamicsWorld(a subclass of btCollisionWorld). To construct the dynamics world we'll need a btConstraintSolver. We'll not look into constraints into this tutorial, but as you can imagine, this class is used to solve constraints (simply said: constraints can be used to attach objects to each other). We use the btDiscreteDynamicsWorld implementation for the dynamics world.

나는 collisionWorld 멤버의 이름을 dynamicsWorld 로 변경했으며 btDynamicsWorld ( btDynamicsWorld 의 하위 클래스) 유형으로 만들었습니다. 역학 세계를 만들기 위해서는 btConstraintSolver 가 필요합니다. 이 튜토리얼에서는 제약 조건을 살펴 보지 않겠지 만 상상할 수있는 것처럼이 클래스는 제약 조건을 해결하는 데 사용됩니다 (단순히 제약 조건을 사용하여 객체를 서로 연결할 수 있음). 우리는 역학 세계에 btDiscreteDynamicsWorld 구현을 사용합니다.

 

After we've created the dynamics world, we also set the gravity of the world. This will cause all dynamic objects (but not the static ground) to have gravity be applied to them. For the sake of our test we'll use a gravity of minus 10 meters along the Y axis per second per second. This is close to earth gravity.

역학 세계를 창조 한 후에도 우리는 세계의 중력을 설정합니다. 이렇게하면 모든 동적 객체 (정적 그라운드가 아닌)에 중력이 적용됩니다. 테스트를 위해 초당 초당 Y 축을 기준으로 10m의 중력을 사용합니다. 이것은 지구의 중력에 가깝습니다.

 

Instead of using the addCollisionObject method, we now use the addRigidBody method to add the objects to the world. Note that the addCollisionObject method is still available, but the addRigidBodymethod makes sure that for example gravity is correctly applied to each object.

Since the objects will now fall down because of gravity, we don't have to move the objects manually. Instead we need to instruct the world to apply the gravity and update the transform of the body.

 

addCollisionObject 메소드를 사용하는 대신 addRigidBody 메소드를 사용하여 오브젝트를 월드에 추가합니다. addCollisionObject 메서드는 계속 사용할 수 있지만 addRigidBody 메서드는 중력이 각 개체에 올바르게 적용되는지 확인합니다.

중력 때문에 객체가 떨어지게되므로 객체를 수동으로 이동할 필요가 없습니다. 대신 우리는 세계에 중력을 적용하고 신체의 변형을 업데이트하도록 지시해야합니다.

 

 @Override
    public void render () {
        final float delta = Math.min(1f / 30f, Gdx.graphics.getDeltaTime());

        dynamicsWorld.stepSimulation(delta, 5, 1f/60f);

        for (GameObject obj : instances)
            obj.body.getWorldTransform(obj.transform);
        ...
    }


View full source on github

 

Instead of the performDiscreteCollisionDetection method we now call the stepSimulation method of the world. This will internally still call the performDiscreteCollisionDetection method (including the collision callbacks).

performDiscreteCollisionDetection 메서드 대신에 우리는 이제 세계의 stepSimulation 메서드를 호출합니다. 내부적으로 여전히 performDiscreteCollisionDetection 메소드 (충돌 콜백 포함)가 호출됩니다.

 

The discrete dynamics world uses a fixed time step. This basically means that it will always use the same delta value to perform calculations. This fixed delta value is supplied as the third argument of stepSimulation. If the actual delta value (the first argument) is greater than the desired fixed delta value, then the calculation will be done multiple times. The maximum number of times that this will be done (the maximum number of sub-steps) is specified by the second argument. Note that we still cap the delta to 1f/30f, so the actual number of sub-steps will never exceed 2. If you want to know more about it, there are quite a few resources on fixing the time-step. However, in practice Bullet takes care of it for us, as long as you understand the arguments, it should be fine.

이산 동역학 세계는 고정 된 시간 단계를 사용합니다. 이것은 기본적으로 항상 동일한 델타 값을 사용하여 계산을 수행한다는 것을 의미합니다. 이 고정 된 델타 값은 stepSimulation 의 세 번째 인수로 제공됩니다. 실제 델타 값 (첫 번째 인수)이 원하는 고정 델타 값보다 큰 경우 계산이 여러 번 수행됩니다. 이 작업이 수행 될 수있는 최대 횟수 (최대 하위 단계)는 두 번째 인수에 의해 지정됩니다. 델타를 여전히 1f/30f 로 제한하므로 실제 하위 단계 수는 2를 초과하지 않습니다. 자세한 내용을 알고 싶으면 시간 단계 수정에 대한 리소스가 충분합니다. 그러나, 실제로 Bullet은 우리를 위해 그것을 처리합니다. 당신이 논쟁을 이해하는 한 그것은 괜찮을 것입니다.

 

Since Bullet now transforms (translates and rotates) our objects, we need to ask Bullet for the new transformation and set it to ModelInstance#transform. This is done using the call to obj.body.getWorldTransform(obj.transform);.

Bullet은 이제 객체를 변형 (변환 및 회전)하므로 Bullet에 새 변형을 요청하고이를 ModelInstance # transform으로 설정해야합니다. 이것은 obj.body.getWorldTransform(obj.transform); 호출을 사용하여 수행됩니다 obj.body.getWorldTransform(obj.transform); .

 

If you run this, you'll see that it actually already does what we want. The objects now fall down with the force of gravity on the static ground. Since we've used a collision filter, as seen in the previous part of this tutorial, the objects don't respond to each other (they fall through each other).

이걸 실행하면 실제로 원하는 것을 이미 수행 한 것을 볼 수 있습니다. 물체는 이제 정적지면에서 중력의 힘으로 떨어집니다. 이 튜토리얼의 이전 파트에서 보았 듯이 충돌 필터를 사용했기 때문에 객체가 서로 반응하지 않습니다 (서로 떨어지게 됨).

Since the world now fully controls the moving objects, there's no need for the moving member of the GameObject class anymore. Therefor we might as well remove that member. I'll not show that change (it's only removing the lines using the moving member), but it does leave us with an empty contact listener. To verify that the contact listener still is triggered, let's change the color of the objects when they hit the ground.

이제 전 세계가 움직이는 객체를 완벽하게 제어하므로 더 이상 GameObject 클래스의 moving 멤버가 필요하지 않습니다. 그러므로 우리는 그 회원을 제거 할 수도 있습니다. 나는 그 변화를 보여주지 않을 것이다. (그것은 moving 멤버를 사용하는 선들 만 제거하고있다.) 그러나 그것은 우리에게 빈 접촉 청취자를 남긴다. 컨택트 리스너가 계속 트리거되는지 확인하려면 오브젝트가 땅에 떨어졌을 때 오브젝트의 색상을 변경합시다.

 

public class BulletTest implements ApplicationListener {
    ...
    class MyContactListener extends ContactListener {
        @Override
        public boolean onContactAdded (int userValue0, int partId0, int index0, int userValue1, int partId1, int index1) {
            if (userValue0 != 0)
                ((ColorAttribute)instances.get(userValue0).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
            if (userValue1 != 0)
                ((ColorAttribute)instances.get(userValue1).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
            return true;
        }
    }
    ...
}


View full source on github

Using motion states

Have a look at following code that we've just added:

    @Override
    public void render () {
        ...
        for (GameObject obj : instances)
            obj.body.getWorldTransform(obj.transform);
        ...
    }


 

Basically this asks bullet the current location and rotation of every object. As you'd imagine, if you have a large world this can be quite some objects that are polled for their location and rotation on every rendercall. However, in practice, there are commonly only just a few objects which are actually moved and/or rotated, if any. Luckily Bullet provides a mechanism to inform us when an object is transformed, so we don't have to iterate through all objects. For this it uses a little callback class called btMotionState.

기본적으로 이것은 모든 객체의 현재 위치와 회전을 총알에 요청합니다. 상상할 수 있듯이, 큰 세계를 가지고 있다면, 이것은 모든 render 호출에서 위치와 회전을 위해 폴링되는 꽤 많은 객체 일 수 있습니다. 그러나 실제로는 일반적으로 실제로 움직이거나 회전 할 수있는 객체가있을뿐입니다. Luckily Bullet은 객체가 변형되었을 때 알려주는 메커니즘을 제공하므로 모든 객체를 반복 할 필요가 없습니다. 이를 위해 btMotionState 라는 작은 콜백 클래스를 사용합니다.

 

The btMotionState class has two methods, which you both must override. The setWorldTransform is called by Bullet whenever it has transformed a dynamic object. The getWorldTransform is called by Bullet whenever it needs to know the current transformation of the object, for example when you add the object to the world.

btMotionState 클래스에는 두 가지 메소드가 있습니다. 두 메소드는 둘 다 대체해야합니다. setWorldTransform 은 동적 객체를 변형 할 때마다 Bullet에 의해 호출됩니다. getWorldTransform 은 오브젝트의 현재 변환 (예 : 오브젝트를 월드에 추가 할 때)을 알아야 할 때마다 불릿에 의해 호출됩니다.

 

    static class MyMotionState extends btMotionState {
        Matrix4 transform;
        @Override
        public void getWorldTransform (Matrix4 worldTrans) {
            worldTrans.set(transform);
        }
        @Override
        public void setWorldTransform (Matrix4 worldTrans) {
            transform.set(worldTrans);
        }
    }


 

Here we have implemented a very basic motion state, simply updating a Matrix4 instance. Of course it is possible to perform other operations as well. In our test, for example, if an object would fall off the ground (the y value of the location is below zero or a certain threshold) we could remove the object from the world.

여기서는 Matrix4 인스턴스를 업데이트하는 매우 기본적인 모션 상태를 구현했습니다. 물론 다른 작업을 수행 할 수도 있습니다. 예를 들어, 우리의 테스트에서, 물체가 바닥에서 떨어진다면 (위치의 y 값이 0 또는 특정 임계 값 이하인 경우) 우리는 물체를 세계에서 제거 할 수 있습니다.

 

Now we need to inform Bullet to use this motion state.

    static class GameObject extends ModelInstance implements Disposable {
        public final btRigidBody body;
        public final MyMotionState motionState;

        public GameObject (Model model, String node, btRigidBody.btRigidBodyConstructionInfo constructionInfo) {
            super(model, node);
            motionState = new MyMotionState();
            motionState.transform = transform;
            body = new btRigidBody(constructionInfo);
            body.setMotionState(motionState);
        }

        @Override
        public void dispose () {
            body.dispose();
            motionState.dispose();
        }
        ...
    }


 

Here we construct the motion state and set its transform to the transform member of the ModelInstance. Note that this is done by reference, so the motion state is directly updating the transform member of the MotionState. The call to setMotionState will inform Bullet to use this motion state. This will also cause Bullet to call the getWorldTransform on the motion state to get the current transform of the object.

여기서 모션 상태를 만들고 ModelInstance의 transform 멤버로 변환을 설정합니다. 이 작업은 참조로 수행되므로 모션 상태는 MotionState의 transform 멤버를 직접 업데이트합니다. setMotionState 를 호출하면 Bullet에이 동작 상태를 사용하도록 알립니다. 또한 Bullet이 모션 상태에서 getWorldTransform 을 호출하여 객체의 현재 변환을 가져옵니다.

We also need to modify the spawn method a bit.

 

    public void spawn () {
        GameObject obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].construct();
        obj.transform.setFromEulerAngles(MathUtils.random(360f), MathUtils.random(360f), MathUtils.random(360f));
        obj.transform.trn(MathUtils.random(-2.5f, 2.5f), 9f, MathUtils.random(-2.5f, 2.5f));
        obj.body.proceedToTransform(obj.transform);
        obj.body.setUserValue(instances.size);
        obj.body.setCollisionFlags(obj.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
        instances.add(obj);
        dynamicsWorld.addRigidBody(obj.body, OBJECT_FLAG, GROUND_FLAG);
    }


 

The only change here is that we removed the call to setWorldTransform on the body and instead now call the proceedToTransform method. This instructs Bullet not only to update the world transformation matrix of the object, but to update all other related members as well.

유일한 변경은 본문에서 setWorldTransform 에 대한 호출을 제거하고 대신에 proceedToTransform 메서드를 호출 proceedToTransform 것입니다. 이렇게하면 Bullet이 개체의 세계 변환 행렬을 업데이트 할뿐 아니라 다른 모든 관련 멤버도 업데이트합니다.

 

Finally we don't have to poll each object for its transform in the render method anymore, so let's remove that code.

마지막으로 우리는 더 이상 render 메소드에서 변환을 위해 각 객체를 폴링 할 필요가 없으므로 해당 코드를 제거합시다.

 

    @Override
    public void render () {
        final float delta = Math.min(1f / 30f, Gdx.graphics.getDeltaTime());

        dynamicsWorld.stepSimulation(delta, 5, 1f/60f);

        if ((spawnTimer -= delta) < 0) {
            spawn();
            spawnTimer = 1.5f;
        }
        ...
    }


View full source on github

 

As you hopefully understand, motion states are very powerful and very easy to work with. There's, however, another advantage of using motion states which might not seem that obvious. To understand this, consider what happens when you call dynamicsWorld.stepSimulation(delta, 5, 1f/60f);. Bullet will perform as many (but not more than the specified maximum) calculations using the specified 1f/60f delta time, until it reaches the specified actual elapsed delta time. Obviously, it's very unlikely that the delta value is always exactly a multiple of 1f/60f. In fact, it is in some cases possible that the value of delta is less than the specified 1f/60f, causing Bullet not to perform any calculations at all.

잘 알고 있듯이 모션 상태는 매우 강력하고 작업하기가 쉽습니다. 그러나 분명하지 않은 모션 상태를 사용하는 또 다른 이점이 있습니다. 이를 이해하려면 dynamicsWorld.stepSimulation(delta, 5, 1f/60f); 을 호출 할 때 어떤 일이 발생하는지 고려하십시오 dynamicsWorld.stepSimulation(delta, 5, 1f/60f); . Bullet은 지정된 1f/60f 델타 시간을 사용하여 지정된 실제 경과 된 델타 시간에 도달 할 때까지 최대 (지정된 최대 값보다 많지는 않음) 계산을 수행합니다. 분명히 delta 값은 항상 정확히 1f/60f 의 배수 일 가능성이 거의 없습니다. 실제로 delta 값이 지정된 1f/60f 보다 작아서 글 머리 기호가 전혀 계산을 수행하지 못하는 경우도 있습니다.

 

Therefor, the transformation which you get using the obj.body.getWorldTransform() method is not the transformation at the current time, but the transformation at the last calculated fixed time step. To compensate for this, Bullet approximates the transformation at the current time by interpolating the calculated transformation. This interpolated transformation is provided to you in the setWorldTransform method of the motion state.

따라서 obj.body.getWorldTransform() 메서드를 사용하여 변환하는 것은 현재 시간의 변환이 아니라 마지막으로 계산 된 고정 시간 단계의 변환입니다. 이를 보완하기 위해 Bullet은 계산 된 변환을 보간하여 현재 시간의 변환을 근사합니다. 이 보간 된 변환은 모션 상태의 setWorldTransform 메소드에서 제공됩니다.

 

So, using a motion state will give you a smoother visual transition between time steps. Note the word visual though, the actual collision detection and dynamics calculation (including callbacks) are only done at the fixed time steps.

따라서 모션 상태를 사용하면 시간 단계간에보다 부드러운 시각적 전환이 가능합니다. 시각적 인 단어에 유의하십시오. 실제 충돌 감지 및 역학 계산 (콜백 포함)은 고정 된 시간 단계에서만 수행됩니다.

A peek behind the scenes

It is great that Bullet provides us the interpolated transformation matrix and while this almost seems trivial it might get you wondering about some of the internals of Bullet. For example, let's have a look at how it has to calculate this interpolated transformation. Consider a ball falling down, just like we have in our test class, but without any other objects. Let's say that the ball starts at location x=0, y=0, z=0, but for simplicity we'll only look at y-coordinate in this example. At the start of the simulation the ball has no velocity (it "falls" down with zero meter per second). We only apply gravity to the ball, which is an acceleration of 10 meter per second per second downwards:

Bullet이 보간 된 변형 행렬을 제공하고 이것이 거의 사소한 것처럼 보이지만 Bullet의 내부 구조에 대해 궁금해하게 할 수 있습니다. 예를 들어 보간 변환을 계산하는 방법을 살펴 보겠습니다. 우리가 테스트 클래스에있는 것처럼, 다른 객체가없는 공이 떨어지는 것을 생각해보십시오. 공이 위치 x = 0, y = 0, z = 0에서 시작한다고 가정 해 봅시다. 간단히하기 위해이 예제에서는 y 좌표만을 살펴 보겠습니다. 시뮬레이션을 시작할 때 볼의 속도는 없습니다 (초당 0 미터로 떨어집니다). 볼에 중력을 가하는 것은 초당 10 미터의 초당 가속도입니다.

 

position(0) = 0f;
velocity(0) = 0f;
acceleration = -10f;


As you probably know, with this information we can calculate the location of the ball at any given time. After one second the velocity is increased with 10 meter per second and the ball has fallen 5 meters downwards, and so on:

우리가 알고있는 것처럼,이 정보로 주어진 시간에 볼의 위치를 ​​계산할 수 있습니다. 1 초가 지나면 속도가 초당 10 미터로 증가하고 공이 5 미터 아래로 떨어지는 등 :

 

position(t) = 0.5 * acceleration * t * t;
position(0) = 0;
position(1) = -5;
position(2) = -20;
position(3) = -45;
position(4) = -80;

While you don't have to understand this equation for this example, I'd recommend to read into it if it doesn't look familiar to you.

 

Now consider that 3.5 seconds have elapsed and we want to know the new location. The most obvious method would be to calculate the new location using above equation: position(3.5) = 0.5 * -10 * 3.5 * 3.5 = -61.25;. While that's the most accurate approach. it isn't feasible in most situations. Practically, this is the same as using a variable time step, which has many disadvantages. For example it misses all collisions that occur between time=0 and time=3.5. And because the delta time will be different each time, the results are not reproducible (you could not replay a physics simulation). Note that you can instruct Bullet to take this approach by providing the value zero for maxSubsteps, which is the second argument of the stepSimulation method, but obviously it is not advised to do so.

이제 3.5 초가 경과했고 새로운 위치를 알고 싶다고 생각해보십시오. 가장 확실한 방법은 위의 방정식을 사용하여 새 위치를 계산하는 것입니다. position(3.5) = 0.5 * -10 * 3.5 * 3.5 = -61.25; . 그 동안 가장 정확한 접근 방법입니다. 그것은 대부분의 상황에서 가능하지 않습니다. 실제적으로, 이것은 많은 단점이있는 가변 시간 단계를 사용하는 것과 동일합니다. 예를 들어, time = 0과 time = 3.5 사이에 발생하는 모든 충돌을 누락시킵니다. 델타 시간은 매번 달라지기 때문에 결과는 재현 할 수 없습니다 (물리 시뮬레이션을 재생할 수는 없습니다). Bullet이 stepSimulation 메소드의 두 번째 인수 인 maxSubsteps에 값 0을 제공하여이 접근 방식을 사용하도록 지시 할 수 있지만 분명히 권장하지는 않습니다.

 

Like discussed before, we're using a fixed time-step. Let's say we have a fixed time step of 1 second. Thus, we calculate the location at time = 1 (and perform collision detection and respond to it). Next, we'll calculate the location at time = 2 (again perform collision detection and respond to it). Finally, we'll calculate the location at time = 3 (and perform collision detection and respond to it). Then we'll somehow have to approximate the location at time = 3.5. Let's see what options that we have:

앞서 논의한 것처럼 우리는 고정 된 시간 단계를 사용하고 있습니다. 우리가 1 초의 고정 된 시간 간격을 가지고 있다고 가정 해 봅시다. 따라서 시간 = 1에서 위치를 계산합니다 (충돌 감지 및 응답). 다음으로 시간 = 2에서 위치를 계산합니다 (다시 충돌 감지를 수행하고 응답합니다). 마지막으로 time = 3에서 위치를 계산하고 충돌 감지를 수행하고 이에 응답합니다. 그러면 우리는 어떻게해서 시간 = 3.5에 위치를 근사해야만 할 것입니다. 우리가 가지고있는 옵션을 보자.

 

  1. We could calculate the location at time = 4 and simply interpolate between them:
position(3.5) = position(3) + 0.5 * (position(4) - position(3)) = -45 + 0.5 * -35 = -62.5

Well, that's pretty close. But it has one big disadvantage: we can't look into the future. To calculate the location at time = 4, we'd have to perform the collision detection and respond to it before it actually happens. This would cause for example the ContactListener to be called while there isn't a contact yet. There are some practical problems with this approach as well, for example interpolation between two transformations can cause strange results

음, 꽤 가깝습니다. 그러나 그것은 하나의 커다란 단점을 가지고 있습니다. 우리는 미래를 내다 볼 수 없습니다. 시간 = 4에서 위치를 계산하려면 실제 충돌 감지 전에 충돌 감지를 수행하고 이에 대응해야합니다. 예를 들어 연락처가 아직없는 동안 ContactListener가 호출 될 수 있습니다. 이 접근법에는 몇 가지 실용적인 문제가 있습니다. 예를 들어 두 변환 간의 보간이 이상한 결과를 초래할 수 있습니다

 

  1. We know the velocity of the ball at time = 3, we could use that to approximate the location at time = 3.5:
velocity(3) = 3 * -10 = -30;
position(3.5) = position(3) + (3.5 - 3) * velocity(3) = -45 + 0.5 * -30 = -60


That's pretty close as well. In fact, it used to be the default in Bullet for quite a while. The main problem with this approach, however, is that the approximated transformation doesn't have to line up with the actual location at time = 4. For example if a collision is ahead or for some other reason the velocity is changed.

꽤 가까워. 사실, Bullet의 기본 설정은 꽤 오래되었습니다. 그러나이 접근법의 주된 문제점은 근사화 된 변환이 시간 = 4에서 실제 위치와 일렬로 정렬 될 필요가 없다는 것입니다. 예를 들어 충돌이 진행 중이거나 다른 이유로 속도가 변경되는 경우입니다.

 

  1. Since the main problem of the previous two approaches is that you can't look into the future, the alternative approach deals with this by literally looking into the past. Instead of approximating the location at time = 3.5, this approach is to approximate the location at time = 2.5:

 앞의 두 가지 접근 방식의 주요 문제점은 미래를 들여다 볼 수 없다는 것인데, 대안적인 접근법은 글자 그대로 과거를 살펴봄으로써이를 처리합니다. 시간 = 3.5에 위치를 근사하는 대신이 방법은 시간 = 2.5에 위치를 근사하는 것입니다.

 

velocity(3) = 3 * -10 = -30;
position(2.5) = position(3) - (3 - 2.5) * velocity(3) = -45 - 0.5 * -30 = -30

 

The key factor to this approach is consistency. The visual representation is always exactly one time step behind. Although this is in most scenario's not noticeable, you could compensate for this in your game logic.

Bullet allows you to choose between approach number 2 and 3 using the setLatencyMotionStateInterpolation method. For example to choose approach 2:

이 접근법의 핵심 요소는 일관성입니다. 시각적 표현은 항상 정확히 한 단계 뒤의 것입니다. 대부분의 시나리오가 눈에 띄지는 않지만 게임 논리에서이를 보완 할 수 있습니다.

Bullet을 사용하면 setLatencyMotionStateInterpolation 메소드를 사용하여 접근 번호 2와 3 중 하나를 선택할 수 있습니다. 예를 들어 접근법 2를 선택하려면 다음과 같이하십시오.

 

((btDiscreteDynamicsWorld)dynamicsWorld).setLatencyMotionStateInterpolation(false);

 

In these approaches I simplified the calculation for readability. In reality, Bullet uses a much more complex and accurate calculation to estimate the desired transformation. The input for this calculation is stored in dedicated members of the collision object, which are called InterpolationWorldTransform, InterpolationLinearVelocity and InterpolationAngularVelocity. You can get and set each of these using the respective methods, for example: object.setInterpolationWorldTransform(transform);. Remember how we changed the spawn() method to use the proceedToTransform method instead of setWorldTransform? The proceedToTransform method will update the interpolation values as well.

이러한 접근 방식에서는 가독성을 위해 계산을 단순화했습니다. 실제로 Bullet은 훨씬 더 복잡하고 정확한 계산을 사용하여 원하는 변환을 추정합니다. 이 계산에 대한 입력은 InterpolationWorldTransform , InterpolationLinearVelocity 및 InterpolationAngularVelocity 라고하는 콜리 전 오브젝트의 전용 멤버에 저장됩니다. 각각의 메소드를 사용하여 각각을 가져 와서 설정할 수 있습니다 object.setInterpolationWorldTransform(transform); 예 : object.setInterpolationWorldTransform(transform); . proceedToTransform 대신에 setWorldTransform 메서드를 사용하도록 spawn() 메서드를 어떻게 변경했는지 기억 setWorldTransform ? proceedToTransform 메서드는 보간 값도 업데이트합니다.

Using contact callback filtering

In the previous part of this tutorial we've used collision filtering to make the objects only collide with the ground. Let's remove this filter and make the objects collide with each other as well.

이 튜토리얼의 이전 부분에서는 충돌 필터링을 사용하여 오브젝트가지면과 충돌 만하도록했습니다. 이 필터를 제거하고 객체를 서로 충돌 시키도록합시다.

 

    public void create () {
        ...
        dynamicsWorld.addRigidBody(object.body);
    }

    public void spawn () {
        ...
        dynamicsWorld.addRigidBody(obj.body);
    }


 

This change should be straight forward, we've only removed the GOUND_FLAG, ALL_FLAG arguments and OBJECT_FLAG, GROUND_FLAG arguments from the call to addRigidBody. If you run this, you'll see that the objects now also collide with each other:

이 변경 사항은 곧바로 진행되어야하며 GOUND_FLAG, ALL_FLAG 호출 GOUND_FLAG, ALL_FLAG 인수 GOUND_FLAG, ALL_FLAG 인수 및 OBJECT_FLAG, GROUND_FLAG 인수 만 제거했습니다. 이것을 실행하면 객체가 서로 충돌한다는 것을 알 수 있습니다.

But the objects now also change to a white color when they collide against another object, while we only want to change the color when they collide with the ground. We can simply solve this by slightly modifying the contact listener:

그러나 객체는 다른 객체와 충돌 할 때 흰색으로 바뀌는 반면, 객체가지면과 충돌 할 때만 색상을 변경하려고합니다. 연락처 리스너를 약간 수정하면됩니다.

 

    class MyContactListener extends ContactListener {
        @Override
        public boolean onContactAdded (int userValue0, int partId0, int index0, int userValue1, int partId1, int index1) {
            if (userValue1 == 0)
                ((ColorAttribute)instances.get(userValue0).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
            if (userValue0 == 0)
                ((ColorAttribute)instances.get(userValue1).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
            return true;
        }
    }


 

Here we change the color of the first object only if the second object is the ground and, likewise, change the color of the second object only if the first object is the ground. Remember that we use the userValuehere which we've set to the index of the object within the instances array. Since the first object is the ground, it has an index of 0.

여기에서는 두 번째 객체가지면 인 경우에만 첫 번째 객체의 색상을 변경하고 마찬가지로 첫 번째 객체가지면 인 경우 두 번째 객체의 색상을 변경합니다. 여기서 우리가 설정 한 userValue instances 배열 내의 객체 색인에 사용한다는 것을 기억하십시오. 첫 번째 객체는지면이므로 0 의 인덱스 0 집니다.

While this works for our test, it isn't optimal. Bullet does not only detect collisions for us, but it now also handles how objects respond to those collisions as well. In most cases, you want Bullet to do it's thing without you being notified for every collision. Only for a small amount of collisions you actually need to be notified. Obviously you can check the collision in the callback, just like we do now in this modified ContactListener. But in that case the wrapper still has to bridge between the native bullet code and your java code for every collision. Luckily the wrapper allows you to specify for which collisions you'd like it to bridge to your ContactListener. This is known as contact callback filtering (sometimes shortened to contact filtering or callback filtering) and is specific to the Bullet wrapper.

이 테스트가 효과적이지만 최적의 것은 아닙니다. Bullet은 우리의 충돌을 감지하지 못하지만 이제는 충돌로 인해 객체가 어떻게 반응하는지도 처리합니다. 대부분의 경우, Bullet은 모든 충돌에 대해 사용자에게 알리지 않고 작업을 수행하기를 원합니다. 소량의 충돌에 대해서만 실제로 통보해야합니다. 분명히 수정 된 ContactListener에서와 마찬가지로 콜백에서 충돌을 확인할 수 있습니다. 그러나이 경우 래퍼는 모든 충돌에 대해 네이티브 총알 코드와 Java 코드 사이를 여전히 연결해야합니다. 운좋게도 래퍼를 사용하면 ContactListener에 연결할 충돌을 지정할 수 있습니다. 이것은 연락처 콜백 필터링 (연락처 필터링 또는 콜백 필터링으로 단축되기도 함)으로 알려져 있으며 Bullet 래퍼에만 해당됩니다.

 

Contact callback filtering works very much like (but is not related to) collision filtering. You need to define a bitwise flag for each collision object and a bitwise filter (mask) for which objects you want the callback to be called.

연락처 콜백 필터링은 충돌 필터링과 매우 유사하지만 충돌 필터링과 관련이 있습니다. 각 콜리 전 객체에 대한 비트 플래그와 콜백을 호출 할 객체에 대한 비트 단위 필터 (마스크)를 정의해야합니다.

 

    public void create () {
        ...
        dynamicsWorld.addRigidBody(object.body);
        object.body.setContactCallbackFlag(GROUND_FLAG);
        object.body.setContactCallbackFilter(0);
    }

    public void spawn () {
        ...
        dynamicsWorld.addRigidBody(obj.body);
        obj.body.setContactCallbackFlag(OBJECT_FLAG);
        obj.body.setContactCallbackFilter(GROUND_FLAG);
    }


 

Here we tell the wrapper that the ground has the bitwise flag GROUND_FLAG (which is the ninth bit as we've seen in the previous part of this tutorial) and that we don't need to be informed when the ground collides with another object (the filter of the ground is zero). Next we tell the wrapper in the spawnmethod that each object has the bitwise flag OBJECT_FLAG and that we want to be informed when it collides with the ground (the filter of the object is GROUND_FLAG). When needed, you can combine multiple flags to create a filter, for example: obj.body.setContactCallbackFilter(GROUND_FLAG | WALL_FLAG);.

여기 래퍼에 땅에 비트 플래그 GROUND_FLAG (이 튜토리얼의 이전 부분에서 보았던 9 번째 비트)가 있고 땅이 다른 오브젝트와 충돌 할 때 GROUND_FLAG 을받을 필요가 없다고 알려줍니다 지면의 필터는 0 임). 다음으로 우리는 spawn 메소드에서 각 객체가 비트 플래그 OBJECT_FLAG 가지고 있고 객체가 땅과 충돌 할 때 (객체의 필터는 GROUND_FLAG ) GROUND_FLAG . 필요한 경우 여러 플래그를 결합하여 필터를 만들 수 있습니다 obj.body.setContactCallbackFilter(GROUND_FLAG | WALL_FLAG); 예 : obj.body.setContactCallbackFilter(GROUND_FLAG | WALL_FLAG); .

 

Note that this is different compared to collision filtering. For collision filtering the filters of both objects need to match the flag of the other object for a collision to occur, while for contact callback filtering only one of the filters has to match with the flag of the other object for the callback to be called.

이것은 충돌 필터링과 다른 점에 유의하십시오. 충돌 필터링의 경우 두 개체의 필터는 충돌이 발생하기 위해 다른 개체의 플래그와 일치해야하지만 연락처 콜백 필터링의 경우 필터 중 하나만 콜백 될 다른 개체의 플래그와 일치해야합니다.

 

Now we need to inform the wrapper to actually use contact callback filtering. We can do this by using another signature for the callback method. Like we've seen in the previous part the ContactListener class has several methods that we can override and depending on the signature of the method that we override the wrapper will optimize where possible.

이제 실제로 컨택 콜백 필터링을 사용하도록 래퍼에 알릴 필요가 있습니다. 우리는 콜백 메소드에 또 다른 서명을 사용하여이를 수행 할 수 있습니다. 이전 부분에서 보았 듯이 ContactListener 클래스에는 재정의 할 수있는 여러 메서드가 있으며 래퍼를 재정의하는 메서드의 시그니처에 따라 가능한 경우 최적화됩니다.

 

    @Override
    public boolean onContactAdded (int userValue0, int partId0, int index0, boolean match0, 
                    int userValue1, int partId1, int index1, boolean match1) {
        if (match0)
            ((ColorAttribute)instances.get(userValue0).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
        if (match1)
            ((ColorAttribute)instances.get(userValue1).materials.get(0).get(ColorAttribute.Diffuse)).color.set(Color.WHITE);
        return true;
    }


View full source on github

 

Here we've changed the method signature to include two boolean arguments: match0 and match1. The wrapper will "see" this and therefor apply contact callback filtering. Note that by default the contact callback filter will be set to zero, so overriding this method without setting the contact callback flag and filter values, will cause the callback never to be triggered. Also note that you can choose whether or not to use contact callback filtering per callback method. For example, the following would use contact callback filtering for the onContactAdded callback, but not for the onContactProcessed callback:

여기서는 메소드 서명을 두 개의 boolean 인수 match0 및 match1 을 포함하도록 변경했습니다. 래퍼는이를보고 "contact callback filtering"을 적용합니다. 기본적으로 연락처 콜백 필터는 0으로 설정되므로 연락처 콜백 플래그 및 필터 값을 설정하지 않고이 메소드를 재정의하면 콜백이 트리거되지 않습니다. 또한 콜백 메소드마다 컨택 콜백 필터링을 사용할지 여부를 선택할 수 있습니다. 예를 들어, 다음은 onContactAdded 콜백에 대한 연락처 콜백 필터링을 사용하지만 onContactAdded 콜백에 대해서는 사용하지 않습니다.

 

    class MyContactListener extends ContactListener {
        @Override
        public boolean onContactAdded (int userValue0, int partId0, int index0, boolean match0,
                        int userValue1, int partId1, int index1, boolean match1) {
            ...
        }
        @Override
        public void onContactProcessed(int userValue0, int userValue1) {
            ...
        }
    }


The match0 and match1 values are used to indicate the filter of which object matches. So in our case, the match variable will only be set for an object colliding with the ground, but not for the ground itself.

match0 및 match1 값은 일치하는 개체의 필터를 나타내는 데 사용됩니다. 따라서 우리의 경우 match 변수는지면과 충돌하는 객체에만 설정되고지면 자체에는 설정되지 않습니다.

Kinematic bodies

Let's make our test a bit more interesting by moving the ground up and down. Since the ground is a static body it will not be affected by physics, but the physics will be affected by the ground. Obviously a moving ground isn't really that static anymore. Such an object that does move, but does not respond to collisions, is called a kinematic body. In practice a kinematic body is very much like a static object, except that you can change its location and rotation through code. Before we can start moving the ground, we'll have to inform Bullet that the ground is now a kinematic body.

바닥을 위아래로 움직여 조금 더 재미있게 테스트 해 봅시다. 지면은 정적 인 몸체이기 때문에 물리학의 영향을받지 않지만 물리학은지면의 영향을받습니다. 분명히 움직이는 땅은 더 이상 정적이지 않습니다. 움직이지 만 충돌에 반응하지 않는 그러한 대상을 기구학이라고합니다. 실제로 운동기구는 코드를 통해 위치와 회전을 변경할 수 있다는 점을 제외하고는 정적 오브젝트와 매우 비슷합니다. 우리가 땅을 움직이기 시작하기 전에 Bullet에게 그 땅이 이제는 운동기구가되었음을 알려야 할 것입니다.

 

    public void create () {
        ...
        instances = new Array<GameObject>();
        GameObject object = constructors.get("ground").construct();
        object.body.setCollisionFlags(object.body.getCollisionFlags()
            | btCollisionObject.CollisionFlags.CF_KINEMATIC_OBJECT);
        instances.add(object);
        dynamicsWorld.addRigidBody(object.body);
        object.body.setContactCallbackFlag(GROUND_FLAG);
        object.body.setContactCallbackFilter(0);
    }


 

The only change here is the call to the setCollisionFlags method, where we add the CF_KINEMATIC_OBJECT flag to the ground body. We've seen this method earlier in the spawn method, where we've used it to add the CF_CUSTOM_MATERIAL_CALLBACK flag to inform Bullet that we want to receive onContactAdded callbacks. Much alike, the CF_KINEMATIC_OBJECT informs Bullet that the ground is a kinematic body and that we might want to change its transformation.

유일한 변경은 setCollisionFlags 메서드에 대한 호출입니다.이 메서드에서는 CF_KINEMATIC_OBJECT 플래그를 지상 본문에 추가합니다. 우리는이 메소드를 spawn 메소드의 앞부분에서 보았습니다. CF_CUSTOM_MATERIAL_CALLBACK 플래그를 추가하여 Bullet에 onContactAdded 콜백을 수신 onContactAdded . 비슷하게, CF_KINEMATIC_OBJECT 는 Bullet에게 그라운드가 기구학 체이며 그 변환을 바꾸고 싶어 CF_KINEMATIC_OBJECT 알려줍니다.

 

So let's move the ground:

    float angle, speed = 90f;
    @Override
    public void render () {
        final float delta = Math.min(1f / 30f, Gdx.graphics.getDeltaTime());

        angle = (angle + delta * speed) % 360f;
        instances.get(0).transform.setTranslation(0, MathUtils.sinDeg(angle) * 2.5f, 0f);
        instances.get(0).body.setWorldTransform(instances.get(0).transform);

        dynamicsWorld.stepSimulation(delta, 5, 1f/60f);
        ...
    }


Here we've added an angle and speed variable. The angle variable will hold the current angle on which we'll base the location of the ground. The speed variable holds the speed in degrees per second at which the ground will move. In the render method we update the angle variable accordingly. Next we set the y coordinate of the location of the ground to the sine of this angle. This will cause the ground to smoothly move up and down. And finally we set the world transform of the physics body of the ground accordingly, causing Bullet to use this new location to perform the next simulation.

여기에 우리가 추가 한 anglespeed변수를. angle변수는 우리가 땅의 위치를 기반으로거야하는 현재의 각도를 개최한다. speed변수는 지상 이동하는 초당 각도 속도를 유지한다. 에서 render방법 우리는 업데이트 angle따라 변수를. 다음으로는, Y이 각도의 사인에 지상의 위치 좌표를 설정. 이 땅이 부드럽게 아래로 이동하게됩니다. 그리고 마지막으로 우리는 세계가 다음 시뮬레이션을 수행하려면이 새 위치를 사용하는 총알의 원인, 그에 따라 지상의 물리학 본체의 변환 설정합니다.

 

If you run this, you'll see that it actually already does what we want. The ground is moving smoothly up and down and the dynamic objects respond accordingly. But after a while, you'll see that the simulation has flaws. Objects are floating in mid-air or are bouncing on the ground.

당신이 이것을 실행하면 실제로 이미 우리가 원하는 것을 볼 수 있습니다. 지상 위아래로 부드럽게 이동 및 동적 객체는 그에 따라 반응한다. 그러나 잠시 후, 당신은 시뮬레이션이 결함을 가지고 있다고 볼 수 있습니다. 객체는 공중에 떠있다거나 바닥에 튀는된다.

To understand why this is happening, imagine a very large static ground with thousands of dynamic objects lying still on that ground. Obviously there's a collision between each dynamic object and the ground, so when collision detection is performed both broad phase and near phase collision algorithms including the contact callbacks will have to be executed for all objects. And this will have to be done every frame for all objects, even while there's no dynamics to be performed.

이런 일이 왜 이해하기 위해, 동적 객체의 수천이 그 땅에 여전히 거짓말과 매우 큰 정적 땅을 상상한다. 물론이 각 동적 오브젝트와 접지 사이의 충돌은, 그래서 충돌 검출이 수행 될 때 다양한 위상과 접촉 콜백 포함 가까운 위상 충돌 알고리즘 모두는 모든 목적에 대해 실행될 것이다. 수행 할 역학 관계가 없습니다 동안에도 그리고이 모든 개체에 대한 모든 프레임을 수행해야합니다.

 

As you might understand, this isn't very effective. That's why Bullet let you specify which objects should be checked for collision. So instead of checking all objects against all objects for collision, Bullet will only check the specified objects against all objects for collision. The objects that should be checked for collision are called active objects. Likewise, objects that shouldn't be checked are called sleeping (or deactivated) objects.

당신이 이해 수 있듯이, 이것은 매우 효과가 없습니다. 총알이 당신이 충돌 검사해야 오브젝트를 지정할 수 있습니다 이유입니다. 그래서 그 대신 검사의 모든 객체에 대한 모든 오브젝트 충돌를 들어, 총알 만 확인합니다 모든 물체에 지정된 개체가 충돌합니다. 충돌 검사해야 객체가 호출되는 활성 개체를. 마찬가지로, 확인 안 객체라고 수면 (또는 비활성화) 객체.

 

While this on itself is a pure collision detection functionality, the dynamics layer makes it extra powerful by automatically activating and deactivating objects as needed. It does this by monitoring the velocity of the objects. When a body is added to the world or when it is moving, it is made active. As soon as its speed gets below a certain threshold (which you can set using the setSleepingThresholds method) for a certain amount of time (which you can set using the setDeactivationTime method), it is candidate for getting deactivated. If all adjacent objects are also not considered active then it is deactivated. This is done through the activation state:

자체에이 순수 충돌 감지 기능이지만, 역학 층은 자동으로 활성화하고 필요에 따라 오브젝트를 비활성화하여 추가 강력합니다. 그것은 물체의 속도를 모니터링하여이 작업을 수행합니다. 몸이 세계 또는 때 이동에 추가되면, 활성화된다. 즉시 그 속도가 (당신이 사용하여 설정할 수 있습니다 특정 임계 값 아래로수록 setSleepingThresholds(당신이 사용하여 설정할 수 있습니다 일정 시간 동안 방법) setDeactivationTime방법), 그것은 비활성화하기위한 후보입니다. 모든 인접한 오브젝트가 활성 고려하지 않을 경우 다음 비활성화됩니다. 이 작업은 활성화 상태를 통해 이루어집니다 :

  • ACTIVE_TAG: the object is active and should be checked for collisions This is the state that all bodies get when they are added to the world and as soon as they are moving.
  • WANTS_DEACTIVATION: the velocity of the object has been below the threshold for the required timeThis state is used internally by Bullet, you should never have to use it.
  • ISLAND_SLEEPING: This object and all adjacent objects are deactivated An island is a group of bodies that are adjacent, for example a stack of boxes all belong to the same island.
  • ACTIVE_TAG: 개체가 활성화되고 충돌을 확인해야 이것은 그들이 세계에 추가하고 이동하는 즉시로 할 때 모든 기관이 얻는 상태입니다.
  • WANTS_DEACTIVATION: 물체의 속도는 필요한 시간에 대한 임계 값 아래로있다 이 상태가 내부적으로 총알에 의해 사용되는, 당신은 그것을 사용하지 않아도됩니다.
  • ISLAND_SLEEPING이 개체 및 모든 인접한 객체 비활성화 섬은 모두 예를 들면, 상자 스택 인접 기관의 그룹은 동일한 섬에 속해있다.

So, in our test class:

  1. an object is added to the world and is set to the ACTIVE_TAG state
  2. gravity applies to the object causing it to fall down, its velocity is above the required threshold thus it is kept in the ACTIVE_TAG state
  3. the object hits the ground causing its velocity to drop below the required threshold, bullet starts counting the time
  4. the velocity has been below threshold for the required time, the state is set to WANTS_DEACTIVATION
  5. all adjacent objects also don't have the ACTIVE_TAG state, thus the state is set to ISLAND_SLEEPING
  6. the object isn't checked for collision anymore
  7. the ground is moved, but it doesn't have the ACTIVE_TAG state, causing the object to float in mid-air
  1. 오브젝트는 세계에 추가되고 설정되어 ACTIVE_TAG상태
  2. 중력이 낙하 유발 물체에 적용, 그 속도는 따라서 필요한 임계치 이상 그것은 유지된다 ACTIVE_TAG상태
  3. 객체가 필요한 임계 값 이하로 떨어질하기 위해 속도를 일으키는 지상 안타, 총알 시간을 계산 시작
  4. 속도가 필요한 시간 임계 값 아래로왔다 상태로 설정된다 WANTS_DEACTIVATION
  5. 모든 인접한 물체도없는 ACTIVE_TAG, 따라서 상태로 설정, 상태를ISLAND_SLEEPING
  6. 객체가 더 이상 충돌에 대해 확인하지 않습니다
  7. 땅은 이동,하지만이없는 ACTIVE_TAG공중에 떠있는 물체를 일으키는 상태를
  8.  

Because the location and rotation of a kinematic body is set by code and it doesn't have a velocity applied, it isn't automatically activated by Bullet. So basically we have to set the state of the kinematic body our self.

역학적 본체의 위치와 회전 코드에 의해 설정되고 그 속도를 적용하지 않기 때문에, 자동 총알 활성화되지 않는다. 그래서 기본적으로 우리는 운동 학적 몸 우리 자신의 상태를 설정해야합니다.

 

    public void render () {
        ...
        instances.get(0).body.setWorldTransform(instances.get(0).transform);
        instances.get(0).body.setActivationState(Collision.ACTIVE_TAG);
        ...
    }


 

For our test this suffices, but this is not the preferred method to make a body active. This is because a timer is used to detect how long the velocity of the body is below the threshold. When activating the body through code, we'd need to reset that timer as well. Luckily bullet has a nice helper method for that called activate():

우리의 테스트를 위해이 충분하지만,이 몸을 활성화하기 위해 선호되는 방법은 아닙니다. 타이머가 몸의 속도가 임계 값보다 시간을 감지하는 데 사용되기 때문입니다. 코드를 통해 몸을 활성화 할 때, 우리는 그뿐만 아니라 타이머를 다시 설정해야 할 것입니다. 다행히 총알은 전화에 대한 좋은 도우미 방법이 있습니다 activate():

 

    public void render () {
        ...
        instances.get(0).body.setWorldTransform(instances.get(0).transform);
        instances.get(0).body.activate();
        ...
    }


 

So, in case we've moved a body, we need to activate that body to make sure that Bullet checks it for collisions. After a short while Bullet will then automatically deactivate the body again. But in our test we're constantly moving the ground, so there's no need for Bullet to automatically deactivate that body. Bullet has two additional activation states for just that:

  • DISABLE_DEACTIVATION: the body will never be (automatically) deactivated
  • DISABLE_SIMULATION: the body will never be (automatically) activated

Thus by setting the activation state to DISABLE_DEACTIVATION right after creating the ground, we don't have to manually activate it in render method anymore.

 

우리가 몸을 이동 한 경우에 그래서, 우리는 충돌에 대한 그 총알을 검사 확인하기 위해 그 몸을 활성화해야합니다. 잠시 총알 후 자동으로 다시 몸을 비활성화합니다. 그러나 우리의 테스트에서 우리는 지속적으로 땅을 이동하고, 그래서 자동으로 몸을 비활성화 총알에 대한 필요가 없습니다. 총알은 단지 두 개의 추가 활성화 상태가 있습니다 :

  • DISABLE_DEACTIVATION : 몸은 (자동) 수 없습니다 비활성화 않습니다
  • DISABLE_SIMULATION : 몸은 (자동) 수 없습니다 활성화 않습니다

따라서에 활성화 상태로 설정하여 DISABLE_DEACTIVATION땅을 만든 후 권리를 우리는 수동으로 활성화 더 이상 방법을 렌더링 할 필요가 없습니다.

 

    public void create () {
        ...
        object.body.setActivationState(Collision.DISABLE_DEACTIVATION);
    }
    ...
    public void render () {
        ...
        instances.get(0).body.setWorldTransform(instances.get(0).transform);

        dynamicsWorld.stepSimulation(delta, 5, 1f/60f);
    }


 

Earlier we've seen how to use motion states to synchronize the world transformation matrices of the bullet physics bodies and our visual game objects. The getWorldTransform of the motion state of an activekinematic body is automatically called by Bullet to get the latest transformation. So we don't have to manually update the body after moving it:

앞서 우리는 총알 물리학 기관의 세계 변환 행렬과 우리의 시각적 게임 객체를 동기화 동작 상태를 사용하는 방법을 보았다. getWorldTransform의 동작 상태의 활성화 학적 본체 자동 최신 변화를 얻을 총알 불린다. 그래서 우리는 수동으로 이동 한 후 시체를 업데이트 할 필요가 없습니다 :

 

    public void render () {
        final float delta = Math.min(1f / 30f, Gdx.graphics.getDeltaTime());

        angle = (angle + delta * speed) % 360f;
        instances.get(0).transform.setTranslation(0, MathUtils.sinDeg(angle) * 2.5f, 0f);

        dynamicsWorld.stepSimulation(delta, 5, 1f/60f);
        ...
    }


View full source on github

 

Be careful: as long as a kinematic body is active, the getWorldTransform method of its motion state is called every time. You should only keep the body activated if you're actually moving or rotating it.

주의 : 한 운동 학적 신체가 활성화되어있는 한, getWorldTransform그 운동 상태의 방법 때마다 호출된다. 당신은 당신이 실제로 이동하거나 회전하는 경우 활성화 된 몸을 유지해야합니다.

What's next

This concludes this second part of the tutorial about the LibGDX Bullet wrapper. Obviously it is impossible to cover every aspect of bullet physics and the libgdx bullet wrapper. For example, it is possible to hook onto the internal time steps of Bullet to get even more control on the simulation. You can use bullet to control your players character. Or you can instruct bullet to use objects as triggers so you're notified about collision, but it doesn't respond to it. And there's a lot more that Bullet has to offer!

이것은 LibGDX 총알 래퍼에 대한 튜토리얼의 두 번째 부분을 결론 지었다. 분명히 총알 물리학의 모든 측면과 libgdx 총알 래퍼를 커버하는 것은 불가능합니다. 예를 들어, 수있다 내부 시간 단계에 끼어 총알 시뮬레이션에 더 많은 제어를 얻기 위해. 당신은 총알을 사용 하여 플레이어 캐릭터를 제어 할 수 . 또는 당신이 사용하는 총알에 지시 할 수 있습니다 트리거 물체를 그래서 당신은 충돌에 대해 통지하고, 그러나 그것은 응답하지 않습니다. 그리고 더 많은 총알이 제공하는있다!

 

If you haven't done so already, I'd highly suggest to read the Bullet manual, it is a good read and the place to start when using the Bullet library. It isn't targeted to the LibGDX Bullet wrapper, but much of it still applies. Also, the Bullet websiteBullet website and especially the Bullet wiki provides a lot of practical information. The forum is also quite active.

아직 수행하지 않은 경우, 나는 높은 읽기 좋을 것 총알 설명서를 , 좋은 읽기와 글 머리 기호 라이브러리를 사용할 때 시작하는 장소입니다. 그것은 LibGDX 총알 래퍼를 대상으로하지만, 대부분은 여전히 적용되지 않습니다. 또한, 총알 웹 사이트 총알 웹 사이트와 특히 총알 위키는 실제 많은 정보를 제공합니다. 포럼은 또한 매우 활성화되어 있습니다.

This wiki page provides a lot of information specifically for the LibGDX Bullet wrapper.

댓글