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

    libGDX. Обработка столкновений. Библиотека Bullet. Часть 3.

    libGDX. Обработка столкновений. Библиотека Bullet. Часть 3.

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

    Создадим класс наследующий ContactListener, переопределим один из вариантов метода onContactAdded и внесем необходимые изменения в код для использования ContactListener.

    public class CollisionsTest extends ApplicationAdapter {
        ......
        Contacter contacter;
        .....
        class Contacter extends ContactListener{
            @Override
            public boolean onContactAdded (btManifoldPoint point, btCollisionObjectWrapper COW0, int partId0, int index0,
                    btCollisionObjectWrapper COW1, int partId1, int index1){
                instances.get(COW0.getCollisionObject().getUserValue()).moving = false;
                instances.get(COW1.getCollisionObject().getUserValue()).moving = false;
                return true;
            }
        }
        
        @Override
        public void create () {
            .....
            contacter = new Contacter();
            .....
        }
        @Override
        public void render () {
            ......
            for(ModelInstanceAdv inst:instances){
                if(inst.moving){
                    inst.transform.trn(0f, -delta, 0f);
                    inst.collObj.setWorldTransform(inst.transform);
                    CheckCollision(inst.collObj, instances.get(0).collObj);
                }
            }
            .....
        }
        
        public void birthObject(){
            ......
            obj.collObj.setUserValue(instances.size);
            obj.collObj.setCollisionFlags(obj.collObj.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
            ......
        }
        @Override
        public void dispose(){
            .....
            contacter.dispose();
        }
    }

    Класс ContactListener не является классом Bullet, а является классом оболочки. Поэтому мы не должны сообщать Bullet о том, что нужно использовать ContactLictener - оболочка позаботится об этом сама. Метод onContactAdded вызывается тогда, когда точка контакта добавляется в множество ManifoldPoint.

    Поскольку библиотека Bullet ничего не знает о нашем классе ModelInstanceAdv, нам нужен способ получить объект этого класса в методе обратного вызова. Для этого используются пользовательские данные (UserData) объекта btCollisionObject. Для установки и получения  индекса объекта используются методы setUserValue и getUserValue. Таким образом, используя instances.get(COW0.getCollisionObject().getUserValue()) мы получаем экземпляр модели в методе onContactAdded.

    В методе birthObject, при создании объекта, мы устанавливаем этот индекс. А также устанавливаем для объекта флаг CF_CUSTOM_MATERIAL_CALLBACK, который сообщает Bullet, что для этого объекта мы хотим получать уведомления о столкновениях.

    В методе render теперь нам не нужно устанавливать флаг движения (это происходит в ContactListener), поэтому оставляем просто вызов CheckCollision. Освобождаем объект в dispose().

    Теперь если запустить код, увидим, что все происходит также как и было, но теперь мы используем ContactListener, вместо постоянного опроса столкновений.

    Вернемся к методу onContactAdded. Он имеет несколько реализаций. Попробуем упростить его. Если посмотреть на код, то мы видим, что btManifoldPoint у нас не используется - уберем его из параметров. Теперь метод будет выглядеть так.

            public boolean onContactAdded (btCollisionObjectWrapper COW0, int partId0, int index0,
                    btCollisionObjectWrapper COW1, int partId1, int index1){
                instances.get(COW0.getCollisionObject().getUserValue()).moving = false;
                instances.get(COW1.getCollisionObject().getUserValue()).moving = false;
                return true;
            }

    В нашем случае нам нет необходимости в использовании в методе именно оболочки объекта столкновений - нам подойдет бы и сам btCollisionObject.

           public boolean onContactAdded (btCollisionObject CO0, int partId0, int index0,
                    btCollisionObject CO1, int partId1, int index1){
                instances.get(CO0.getUserValue()).moving = false;
                instances.get(CO1.getUserValue()).moving = false;
                return true;
            }

    Более того, мы можем получить доступ к экземпляру модели используя только пользовательской значение UserValue.

            public boolean onContactAdded (int userValue0, int partId0, int index0,
                    int userValue1, int partId1, int index1){
                instances.get(userValue0).moving = false;
                instances.get(userValue1).moving = false;
                return true;
            }

    Есть и другие реализации этого метода, их можно посмотреть в исходном коде класса ContactListener. Но использовать мы можем только одну из них, т.е переопределить несколько реализаций метода нельзя.

    Теперь мы получаем уведомления, когда происходит столкновение, но все еще проверяем каждый объект на столкновение с землей методом CheckCollision. В нем при каждой проверке создаются и уничтожаются некоторые объекты, что может вызвать подвисания. К тому же в методе используются специализированные алгоритмы проверки на столкновения, которые довольно дороги, в смысле процессорного времени. Правильнее было бы сначала сделать грубую оценку - находятся ли объекты рядом, а потом, при необходимости, применять к ним более точные специализированные алгоритмы. Таким образом проверка будет проходить в две фазы - широкая фаза (грубая оценка) и ближняя фаза (специализированные алгоритмы). До этого в нашем коде применялась только проверка в ближней фазе, для которой используется диспетчер столкновений.

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

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

        btBroadphaseInterface broadphase;
        btCollisionWorld collWorld;
        
        public void create () {
            .....
            ModelInstanceAdv object = constructors.get("ground").create();
            instances.add(object);
            collisionConf = new btDefaultCollisionConfiguration();
            collisionDispatcher = new btCollisionDispatcher(collisionConf);
            broadphase = new btDbvtBroadphase();
            collWorld = new btCollisionWorld(collisionDispatcher, broadphase, collisionConf);
            contacter = new Contacter();
            collWorld.addCollisionObject(object.collObj);
        }
        @Override
        public void render () {
           .....
            for(ModelInstanceAdv inst:instances){
                if(inst.moving){
                    inst.transform.trn(0f, -delta, 0f);
                    inst.collObj.setWorldTransform(inst.transform);
                }
            }
            collWorld.performDiscreteCollisionDetection();
           ......
        }
        
        public void birthObject(){
            .....
            instances.add(obj);
            collWorld.addCollisionObject(obj.collObj);
        }
        @Override
        public void dispose(){
            .....
            collWorld.dispose();
            broadphase.dispose();
            .....
        }
    }

    Создаем два объекта btBroadphaseInterface (интерфейс широкой фазы) и btCollisionWorld. В create() инициализируем их. Причем btBroadphaseInterface инициализируем с помощью класса btDbvtBroadphase (одна из реализаций "AABB tree"). А в btCollisionWorld передаем диспетчер (для обработки ближней фазы), broadphase (для обработки широкой фазы) и конфигурацию столкновений. Затем добавляем в btCollisionWorld объект столкновений экземпляра модели, для которого требуется отслеживание столкновений. Также добавляем объекты столкновений при создании падающих объектов в методе birthObject.

    В render() удаляем вызов CheckCollision (и, соответственно, удаляем, ставший больше не нужным, сам метод CheckCollision). Вместо него вызываем collWorld.performDiscreteCollisionDetection(). Теперь btCollisionWorld будет обнаруживать столкновения. Не забываем уничтожить объекты в dispose().

    Запустим приложение и видим, что некоторые падающие объекты зависают в воздухе не достигнув поверхности земли. Это происходит потому, что btCollisionWorld обрабатывает столкновения всех объектов, т.е при касании падающих объектов друг друга они останавливаются. Исправим это. 

           public boolean onContactAdded (int userValue0, int partId0, int index0,
                    int userValue1, int partId1, int index1){
                if(userValue0 == 0) instances.get(userValue1).moving = false;
                if(userValue1 == 0)instances.get(userValue0).moving = false;
                return true;
            }

    Мы знаем, что земля имеет userValue равное нулю, поэтому останавливать будем объект только в том случае, если один из userValue имеет значение ноль.

    Несмотря на то, что такой способ работает, все же это не самое правильное решение.  Было бы лучше, если бы btCollisionWorld вообще игнорировал проверку столкновений падающих объектов друг с другом. Для этого применяются битовые маски, с помощью которых мы можем сообщить btCollisionWorld с какими объектами может сталкиваться данный объект.

        final static short GROUND_FLAG = 1 << 8;
        final static short OBJECT_FLAG = 1 << 9;
        final static short ALL_FLAG = -1;
        .....
        public void create () {
            .....
            collWorld.addCollisionObject(object.collObj, GROUND_FLAG, ALL_FLAG);
        }
       public void birthObject(){
            .....
            collWorld.addCollisionObject(obj.collObj, OBJECT_FLAG, GROUND_FLAG);
        }

    Создаем три флага типа short. У GROUND_FLAG установлен только девятый бит, у OBJECT_FLAG только десятый, а у ALL_FLAG установлены все биты. При передаче объекта столкновений в btCollisionWorld указываем битовую маску для самого объекта и маску, в которой указано с объектами какого может сталкиваться объект. Таким образом, земля может сталкиваться со всеми объектами, а падающие объекты будут сталкиваться только с землей, т.е. btCollisionWorld будет реагировать только на столкновения с объектами, у которых установлен девятый бит, а остальные игнорировать.

    Почему мы использовали биты старше восьмого? Дело в том, что в некоторых случаях Bullet использует первый байт маски для собственных нужд. И хотя в нашем конкретном случае первый байт свободен, лучше его не использовать. Таким образом, в распоряжении программиста остается только второй байт (short - двухбайтовая переменная). Принимая это во внимание, при большом количестве различных групп объектов нужно тщательнее продумывать их битовые маски.

    В статье использованы материалы туториала 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.Vector3;
    import com.badlogic.gdx.physics.bullet.Bullet;
    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.btCollisionWorld;
    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.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;
        btCollisionWorld collWorld;
        final static short GROUND_FLAG = 1 << 8;
        final static short OBJECT_FLAG = 1 << 9;
        final static short ALL_FLAG = -1;
        
        static class ModelInstanceAdv extends ModelInstance implements Disposable{
            public final btCollisionObject collObj;
            public boolean moving;
            public ModelInstanceAdv(Model model, String node, btCollisionShape collShape) {
                super(model, node);
                collObj = new btCollisionObject();
                collObj.setCollisionShape(collShape);
            }
            @Override
            public void dispose() {
                collObj.dispose();
            }
            
            static class Constructor implements Disposable{
                public final Model model;
                public final String node;
                public final btCollisionShape collShape;
                public Constructor(Model model, String node, btCollisionShape collShape){
                    this.model = model;
                    this.node = node;
                    this.collShape = collShape;
                }
                
                public ModelInstanceAdv create(){
                    return new ModelInstanceAdv(model, node, collShape);
                }
                @Override
                public void dispose() {
                    collShape.dispose();                
                }
            }
        }
        
        class Contacter extends ContactListener{
            @Override
            public boolean onContactAdded (int userValue0, int partId0, int index0,
                    int userValue1, int partId1, int index1){
                instances.get(userValue0).moving = false;
                instances.get(userValue1).moving = false;
                return true;
            }
        }
        
        @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))));
            constructors.put("ball", new ModelInstanceAdv.Constructor(model, "ball", new btSphereShape(0.5f)));
            constructors.put("box", new ModelInstanceAdv.Constructor(model, "box", new btBoxShape(new Vector3(0.5f, 0.5f, 0.5f))));
            constructors.put("cone", new ModelInstanceAdv.Constructor(model, "cone", new btConeShape(0.5f, 2f)));
            constructors.put("cylinder", new ModelInstanceAdv.Constructor(model, "cylinder", new btCylinderShape(new Vector3(0.5f, 1f, 0.5f))));
            constructors.put("capsule", new ModelInstanceAdv.Constructor(model, "capsule", new btCapsuleShape( 0.5f, 1f)));
            instances = new Array<ModelInstanceAdv>();
            ModelInstanceAdv object = constructors.get("ground").create();
            instances.add(object);
            
            collisionConf = new btDefaultCollisionConfiguration();
            collisionDispatcher = new btCollisionDispatcher(collisionConf);
            broadphase = new btDbvtBroadphase();
            collWorld = new btCollisionWorld(collisionDispatcher, broadphase, collisionConf);
            contacter = new Contacter();
            collWorld.addCollisionObject(object.collObj, GROUND_FLAG, ALL_FLAG);
        }
        @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());
            for(ModelInstanceAdv inst:instances){
                if(inst.moving){
                    inst.transform.trn(0f, -delta, 0f);
                    inst.collObj.setWorldTransform(inst.transform);
                }
            }
            collWorld.performDiscreteCollisionDetection();
            if((birthTime -= delta) < 0){
                birthObject();
                birthTime = 1.5f;
            }
            modelBatch.begin(cam);
            modelBatch.render(instances, environment);
            modelBatch.end();
        }
        
        public void birthObject(){
            ModelInstanceAdv obj = constructors.values[1 + MathUtils.random(constructors.size - 2)].create();
            obj.moving = true;
            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.collObj.setWorldTransform(obj.transform);
            obj.collObj.setUserValue(instances.size);
            obj.collObj.setCollisionFlags(obj.collObj.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
            instances.add(obj);
            collWorld.addCollisionObject(obj.collObj, OBJECT_FLAG, GROUND_FLAG);
        }
        @Override
        public void dispose(){
            for(ModelInstanceAdv inst:instances)inst.dispose();
            instances.clear();
            for(ModelInstanceAdv.Constructor cons:constructors.values())cons.dispose();
            constructors.clear();
            collWorld.dispose();
            broadphase.dispose();
            collisionDispatcher.dispose();
            collisionConf.dispose();
            contacter.dispose();
            modelBatch.dispose();
            model.dispose();
        }
    }

    libGDX. Обработка столкновений. Библиотека Bullet. Часть 2.
    libGDX. Обработка столкновений. Библиотека Bullet.
    libGDX. Интерактивное взаимодействие с 3D объектами.
    libGDX. Отбраковка объектов не попадающих в обзор камеры.
    3D модель  для libGDX. Пишем код.
    3D модель для движка libGDX
    Строим модель с помощью ModelBuilder
    libGDX. Основы 3D программирования.
    Игра Flower. Ловим капли.
    TexturePacker.Создаем атлас текстур.
    Создаем проект на движке libGDX
    Кастомизация EditText
    Кастомизация SeekBar'а
    9-patch изображения для Андроид
    Кастомный ползунок в виде дуги (аналог SeekBar)

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

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