Вторник, 12.12.2017, 09:22
Поиск
Никнэйм
Сертификат на никнейм Olelucoye, зарегистрирован на Тимофеев Константин Михайлович
Зарегистрируй свой никнейм
Обратная связь
olelucoye.tk@yandex.ru
Реклама AdSense
Реклама
Друзья сайта
  • Лига медицинского права
  • Гостиница "Зай"
  • FAQ по системе
  • Инструкции для uCoz
  • Главная » Статьи » libGDX

    libGDX. Bullet. Динамика твердого тела. Часть 2.

    libGDX. Bullet. Динамика твердого тела. Часть 2.

    Продолжаем исследование динамики твердого тела, начатое в предыдущей статье. Наши объекты падают на землю по действием гравитации, проявляют свойства физических тел, но все еще не реагируют на столкновения друг с другом и проникают сквозь друг друга. Это потому, что у нас все еще действует фильтрация столкновений. Уберем её, чтобы объекты могли сталкиваться между собой.

        public void create () {
           ......
            dynWorld.addRigidBody(object.body);
           ......
        }
        public void birthObject(){
            .....
            dynWorld.addRigidBody(obj.body);
            .....
        }

    Мы просто удаляем битовые флаги и теперь объекты могут взаимодействовать между собой.

    В большинстве случаев, нам нет необходимости получать уведомления о всех столкновениях. В нашем примере нам нужно получать сообщения только о столкновениях объектов с землей, а обработку остальных можно возложить на Bullet. Это можно сделать в ContactListener, но при этом происходит преобразование между кодом Bullet  и Java. Мы можем отфильтровать часть столкновений, указав Bullet о каких столкновениях мы хотим получать уведомления в ContactListener. Для этого нам снова понадобятся те же битовые флаги.

       public void create () {
            ....
            dynWorld.addRigidBody(object.body);
            object.body.setContactCallbackFlag(GROUND_FLAG);
            object.body.setContactCallbackFilter(0);
        }
        public void birthObject(){
            .....
            dynWorld.addRigidBody(obj.body);
            obj.body.setContactCallbackFlag(OBJECT_FLAG);
            obj.body.setContactCallbackFilter(GROUND_FLAG);
        }

    Устанавливаем флаг объекта и флаг для фильтра сообщений. Очень похоже на фильтр столкновений, но есть отличие - при указании параметра фильтра мы можем комбинировать несколько флагов (например, obj.body.setContactCallbackFilter(GROUND_FLAG | WALL_FLAG)), что удобно когда нам нужно обрабатывать столкновения с несколькими видами объектов. Ноль в параметре фильтра означает, что для этого объекта уведомления о столкновениях мы получать не будем.

    Теперь нам нужно сообщить ContactListener о использовании фильтрации сообщений.

          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;
            }

     

    Для этого мы используем другую реализацию onContactAdded, с дополнительными boolean параметрами. True для match0 или match1 будет установлен для объекта вызвавшего событие столкновения.

    Нужно заметить. что нам предоставляется возможность выбирать использовать фильтрацию или нет. Например, в следующем примере onContactAdded использует фильтрацию, а onContactProcessed нет.

        class Contacter extends 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;
            }
            @Override
            public void onContactProcessed(int userValue0, int userValue1){
                ......
            }
        }

    Усложним ситуацию - добавим земле движение вверх-вниз. Поскольку земля у нас статический объект, она не будет зависеть от физики. Объекты, которые движутся, но не реагируют на столкновения, называются кинематическими. Кинематические объекты - это те же статические, за исключением того. что мы можем изменить их положение и поворот в коде.

    Прежде чем начать передвигать землю, нужно сообщить Bullet, что земля будет кинетическим объектом.

        public void create () {
            ......
            ModelInstanceAdv object = constructors.get("ground").create();
            object.body.setCollisionFlags(object.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_KINEMATIC_OBJECT);
            instances.add(object);
            .....
        }

    Для этого нужно установить флаг CF_KINEMATIC_OBJECT объекту методом setCollisionFlags.

    Начнем двигать землю:

        float angle, speed = 90f;
        ......
        public void render () {
            ......
            final float delta = Math.min(1f/30f, Gdx.graphics.getDeltaTime());
            angle = (angle + delta*speed)%360f;
            instances.get(0).transform.setTranslation(0f, MathUtils.sinDeg(angle) * 2.5f, 0f);
            instances.get(0).body.setWorldTransform(instances.get(0).transform);
            dynWorld.stepSimulation(delta, 5, 1f/60f);
            ......
        }

    Объявляем переменные угла и скорости. Создаем нехитрую формулу, результатом которой будет плавно изменяющийся угол. Перемещаем землю на величину синуса этого угла. Передаем матрицу перемещения объекту твердого тела. В результате земля перемещается вверх-вниз. Запустим и посмотрим, что получилось.

    Мы видим, что некоторые объекты зависают в воздухе. Посмотрим. почему так происходит. При большом количестве объектов Bullet приходится обновлять всё это множество объектов, хотя большинство из них неподвижны. В целях экономии ресурсов Bullet перестает следить за объектами, которые определенное время остаются без движения. Работает этот механизм так: когда объект движется - он активен, когда его скорость падает ниже определенного порога (который можно установить методом setSleepingThreshold) в течение определенного периода времени (который можно установить методом setDeactivationTime) объект становится кандидатом на деактивацию. Затем проверяются соседние объекты - если они тоже неподвижны, объект деактивируется. Для этого используются флаги деактивации:

    ACTIVE_TAG - объект активен и должен проверяться на столкновения,

    WANTS_DEACTIVATION - скорость объекта была ниже порога в течении определенного времени. Этот флаг используется Bullet, его нам использовать нельзя,

    ISLAND_SLEEPING - объект и все соседние объекты неподвижны.

    Земля передвигается, но не имеет флага ACTIVE_TAG, так как перемещение кинематических объектов не управляется Bullet , поэтому не учитывается при проверке соседних объектов. Но нам предоставляется возможность самим установить этот флаг для кинематического объекта.

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

    Либо можно воспользоваться методом activate() (что является более предпочтительным).

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

    Bullet будет проверять активированный объект на движение и, если он будет неподвижен некоторое время, Bullet его отключит. В нашем случае земля постоянно находится в движении и деактивирована не будет.

    Также мы можем использовать дополнительные состояния активации объектов:

    DISABLE_DEACTIVATION - объект никогда не будет автоматически деактивирован,

    DISABLE_SIMULATION - объект никогда не будет автоматически активирован.

    Мы можем установить флаг DISABLE_DEACTIVATION объекту земли сразу после создания и нам не нужно будет больше активировать объект вручную в render().

       public void create () {
            ......
            object.body.setActivationState(Collision.DISABLE_DEACTIVATION);
        }
        public void render () {
            .....
            angle = (angle + delta*speed)%360f;
            instances.get(0).transform.setTranslation(0f, MathUtils.sinDeg(angle) * 2.5f, 0f);
           
            dynWorld.stepSimulation(delta, 5, 1f/60f);
           .....
        }

    К тому же мы удаляем setWorldTransform для объекта твердого тела земли, потому что Bullet автоматически обновляет состояние активного кинематического объекта методом getWorldTransform.

    Запускаем - все работает как нужно.

    В статье использованы материалы туториала xoppa.

    Полный листинг главного класса.

    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.PerspectiveCamera;
    import com.badlogic.gdx.graphics.VertexAttributes.Usage;
    import com.badlogic.gdx.graphics.g3d.Environment;
    import com.badlogic.gdx.graphics.g3d.Material;
    import com.badlogic.gdx.graphics.g3d.Model;
    import com.badlogic.gdx.graphics.g3d.ModelBatch;
    import com.badlogic.gdx.graphics.g3d.ModelInstance;
    import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
    import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
    import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
    import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
    import com.badlogic.gdx.math.MathUtils;
    import com.badlogic.gdx.math.Matrix4;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.physics.bullet.Bullet;
    import com.badlogic.gdx.physics.bullet.collision.Collision;
    import com.badlogic.gdx.physics.bullet.collision.ContactListener;
    import com.badlogic.gdx.physics.bullet.collision.btBoxShape;
    import com.badlogic.gdx.physics.bullet.collision.btBroadphaseInterface;
    import com.badlogic.gdx.physics.bullet.collision.btCapsuleShape;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionConfiguration;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionDispatcher;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionObject;
    import com.badlogic.gdx.physics.bullet.collision.btCollisionShape;
    import com.badlogic.gdx.physics.bullet.collision.btConeShape;
    import com.badlogic.gdx.physics.bullet.collision.btCylinderShape;
    import com.badlogic.gdx.physics.bullet.collision.btDbvtBroadphase;
    import com.badlogic.gdx.physics.bullet.collision.btDefaultCollisionConfiguration;
    import com.badlogic.gdx.physics.bullet.collision.btDispatcher;
    import com.badlogic.gdx.physics.bullet.collision.btSphereShape;
    import com.badlogic.gdx.physics.bullet.dynamics.btConstraintSolver;
    import com.badlogic.gdx.physics.bullet.dynamics.btDiscreteDynamicsWorld;
    import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld;
    import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody;
    import com.badlogic.gdx.physics.bullet.dynamics.btSequentialImpulseConstraintSolver;
    import com.badlogic.gdx.physics.bullet.linearmath.btMotionState;
    import com.badlogic.gdx.utils.Array;
    import com.badlogic.gdx.utils.ArrayMap;
    import com.badlogic.gdx.utils.Disposable;
    public class CollisionsTest extends ApplicationAdapter {
        public PerspectiveCamera cam;
        public Model model;
        public Array<ModelInstanceAdv> instances;
        public ArrayMap<String, ModelInstanceAdv.Constructor> constructors;
        public ModelBatch modelBatch;
        public Environment environment;
        public CameraInputController camController;
        boolean collision_flag;
        btCollisionShape groundShape, thingShape;
        btCollisionObject groundObject, thingObject;
        btCollisionConfiguration collisionConf;
        btDispatcher collisionDispatcher;
        float birthTime;
        Contacter contacter;
        btBroadphaseInterface broadphase;
        btDynamicsWorld dynWorld;
        btConstraintSolver solver;
        final static short GROUND_FLAG = 1 << 8;
        final static short OBJECT_FLAG = 1 << 9;
        final static short ALL_FLAG = -1;
        float angle, speed = 90f;
        
        static class ModelInstanceAdv extends ModelInstance implements Disposable{
            public final btRigidBody body;
            public final MotionStateAdv motionState;
            
            public ModelInstanceAdv(Model model, String node, btRigidBody.btRigidBodyConstructionInfo bodyInfo) {
                super(model, node);
                body = new btRigidBody(bodyInfo);
                motionState = new MotionStateAdv();
                motionState.transform = transform;
                body.setMotionState(motionState);
            }
            @Override
            public void dispose() {
                body.dispose();
                motionState.dispose();
            }
            
            static class Constructor implements Disposable{
                public final Model model;
                public final String node;
                public final btCollisionShape collShape;
                public final btRigidBody.btRigidBodyConstructionInfo bodyInfo;
                private static Vector3 inertia = new Vector3();
                public Constructor(Model model, String node, btCollisionShape collShape, float mass){
                    this.model = model;
                    this.node = node;
                    this.collShape = collShape;
                    if(mass > 0f){
                        collShape.calculateLocalInertia(mass, inertia);
                    }else{inertia.set(0f, 0f, 0f);}
                    this.bodyInfo = new btRigidBody.btRigidBodyConstructionInfo(mass, null, collShape, inertia);
                }
                
                public ModelInstanceAdv create(){
                    return new ModelInstanceAdv(model, node, bodyInfo);
                }
                @Override
                public void dispose() {
                    collShape.dispose();
                    bodyInfo.dispose();
                }
            }
        }
        
        class Contacter extends 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;
            }
            @Override
            public void onContactProcessed(int userValue0, int userValue1){
                
            }
        }
        
        static class MotionStateAdv extends btMotionState{
            Matrix4 transform;
            
            @Override
            public void getWorldTransform(Matrix4 WorldTrans){
                WorldTrans.set(transform);            
            }
            @Override
            public void setWorldTransform(Matrix4 WorldTrans){
                transform.set(WorldTrans);
            }
        }
        
        @Override
        public void create () {
            Bullet.init();
            
            modelBatch = new ModelBatch();
            environment = new Environment();
            environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
            environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
            cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
            cam.position.set(10f, 10f, 10f);
            cam.lookAt(0f, 0f, 0f);
            cam.near = 1f;
            cam.far = 300f;
            cam.update();
            camController = new CameraInputController(cam);
            Gdx.input.setInputProcessor(camController);
            
            ModelBuilder modelBuilder = new ModelBuilder();
            modelBuilder.begin();
            modelBuilder.node().id = "ground";
            modelBuilder.part("box", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.GRAY))).box(10f, 0.1f, 10f);
            modelBuilder.node().id = "ball";
            modelBuilder.part("ball", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.RED))).sphere(1f, 1f, 1f, 20, 20);
            modelBuilder.node().id = "box";
            modelBuilder.part("box", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.GREEN))).box(1f, 1f, 1f);
            modelBuilder.node().id = "cone";
            modelBuilder.part("cone", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.BLUE))).cone(1f, 2f, 1f, 20);
            modelBuilder.node().id = "cylinder";
            modelBuilder.part("cylinder", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.YELLOW))).cylinder(1f, 2f, 1f, 20);
            modelBuilder.node().id = "capsule";
            modelBuilder.part("capsule", GL20.GL_TRIANGLES, Usage.Position|Usage.Normal, new Material(ColorAttribute.createDiffuse(Color.MAGENTA))).capsule(0.5f, 2f, 20);
            model = modelBuilder.end();
            
            constructors = new ArrayMap<String, ModelInstanceAdv.Constructor>(String.class, ModelInstanceAdv.Constructor.class);
            constructors.put("ground", new ModelInstanceAdv.Constructor(model, "ground", new btBoxShape(new Vector3(5f, 0.05f, 5f)), 0f));
            constructors.put("ball", new ModelInstanceAdv.Constructor(model, "ball", new btSphereShape(0.5f), 1f));
            constructors.put("box", new ModelInstanceAdv.Constructor(model, "box", new btBoxShape(new Vector3(0.5f, 0.5f, 0.5f)), 1f));
            constructors.put("cone", new ModelInstanceAdv.Constructor(model, "cone", new btConeShape(0.5f, 2f), 1f));
            constructors.put("cylinder", new ModelInstanceAdv.Constructor(model, "cylinder", new btCylinderShape(new Vector3(0.5f, 1f, 0.5f)), 1f));
            constructors.put("capsule", new ModelInstanceAdv.Constructor(model, "capsule", new btCapsuleShape( 0.5f, 1f), 1f));
            
            
            collisionConf = new btDefaultCollisionConfiguration();
            collisionDispatcher = new btCollisionDispatcher(collisionConf);
            broadphase = new btDbvtBroadphase();
            solver = new btSequentialImpulseConstraintSolver();
            
            dynWorld = new btDiscreteDynamicsWorld(collisionDispatcher, broadphase, solver, collisionConf);
            dynWorld.setGravity(new Vector3(0f, -10f, 0f));
            contacter = new Contacter();
            instances = new Array<ModelInstanceAdv>();
            ModelInstanceAdv object = constructors.get("ground").create();
            object.body.setCollisionFlags(object.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_KINEMATIC_OBJECT);
            instances.add(object);
            dynWorld.addRigidBody(object.body);
            object.body.setContactCallbackFlag(GROUND_FLAG);
            object.body.setContactCallbackFilter(0);
            object.body.setActivationState(Collision.DISABLE_DEACTIVATION);
        }
        
        @Override
        public void render () {
            Gdx.gl.glViewport ( 0 , 0 , Gdx.graphics.getWidth (), Gdx.graphics.getHeight ());
            Gdx.gl.glClearColor(0.3f, 0.5f, 1f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
            final float delta = Math.min(1f/30f, Gdx.graphics.getDeltaTime());
            angle = (angle + delta*speed)%360f;
            instances.get(0).transform.setTranslation(0f, MathUtils.sinDeg(angle) * 2.5f, 0f);
            //instances.get(0).body.setWorldTransform(instances.get(0).transform);
            //instances.get(0).body.activate();
            dynWorld.stepSimulation(delta, 5, 1f/60f);
            if((birthTime -= delta) < 0){
                birthObject();
                birthTime = 1.5f;
            }
            camController.update();
                    modelBatch.begin(cam);
            modelBatch.render(instances, environment);
            modelBatch.end();
        }
        
        public void birthObject(){
            ModelInstanceAdv obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].create();
            obj.transform.setFromEulerAngles(MathUtils.random(360f), MathUtils.random(360f), MathUtils.random(360f));
            obj.transform.trn(MathUtils.random(5f, -5f), 9f, MathUtils.random(5f, -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);
            dynWorld.addRigidBody(obj.body);
            obj.body.setContactCallbackFlag(OBJECT_FLAG);
            obj.body.setContactCallbackFilter(GROUND_FLAG);
        }
        @Override
        public void dispose(){
            for(ModelInstanceAdv inst:instances)inst.dispose();
            instances.clear();
            for(ModelInstanceAdv.Constructor cons:constructors.values())cons.dispose();
            constructors.clear();
            dynWorld.dispose();
            solver.dispose();
            broadphase.dispose();
            collisionDispatcher.dispose();
            collisionConf.dispose();
            contacter.dispose();
            modelBatch.dispose();
            model.dispose();
        }
    }

    libGDX. Bullet. Динамика твердого тела.
    libGDX. Обработка столкновений. Библиотека Bullet. Часть 3.
    libGDX. Обработка столкновений. Библиотека Bullet. Часть 2.
    libGDX. Обработка столкновений. Библиотека Bullet.
    libGDX. Интерактивное взаимодействие с 3D объектами.
    libGDX. Отбраковка объектов не попадающих в обзор камеры.
    3D модель  для libGDX. Пишем код.
    3D модель для движка libGDX
    Строим модель с помощью ModelBuilder
    libGDX. Основы 3D программирования.
    Игра Flower. Ловим капли.
    TexturePacker.Создаем атлас текстур.
    Создаем проект на движке libGDX
    Диктофон с дизайном под металл
    Кастомизация EditText
    Кастомизация SeekBar'а
    9-patch изображения для Андроид
    Кастомный ползунок в виде дуги (аналог SeekBar)
    Анимация в Андроид
    Кастомизация элементов управления в Android
    Смартфон DEXP Ixion ML 5, обзор.
    Создание кастомного View-элемента интерфейса.
    Создание виджета - электронные часы с кастомным шрифтом
    Будильник для Андроид "Разбуди меня"
    Программируем калькулятор на андроид. Урок 1.

    Категория: libGDX | Добавил: Olelucoye (05.05.2015)
    Просмотров: 2393
    | Теги: 3D, libGDX, объект твердого тела, ActivationState, DynamicsWorld, Rigidbody | Рейтинг: 0.0/0
    Всего комментариев: 0
    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Меню сайта
    Категории раздела
    Андроид разработка [23]
    libGDX [24]
    Мои андроид проекты [6]
    Excel [7]
    Железяки [5]
    Скрипты в блокноте [4]
    Разное [1]
    Форма входа
    Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Яндекс Метрика
    Яндекс.Метрика