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

    libGDX. Интерактивное взаимодействие с 3D объектами.

    libGDX. Интерактивное взаимодействие с 3D объектами.

    В этой статье рассмотрим как взаимодействовать с 3D объектами посредством мыши или касания (для сенсорных устройств) - будем выделять их и перетаскивать. Работать продолжим с кодом из предыдущей статьи. Для обработки событий ввода нам понадобится реализовать интерфейс InputProcessor.

    public class Proba3D extends ApplicationAdapter implements InputProcessor {
    ........
        @Override
        public boolean keyDown(int keycode) {
            return false;
        }
        @Override
        public boolean keyUp(int keycode) {
            return false;
        }
        @Override
        public boolean keyTyped(char character) {
            return false;
        }
        @Override
        public boolean touchDown(int screenX, int screenY, int pointer, int button) {
           return false;
        }
        @Override
        public boolean touchUp(int screenX, int screenY, int pointer, int button) {
            return false;
        }
        @Override
        public boolean touchDragged(int screenX, int screenY, int pointer) {
            return false;
        }
        @Override
        public boolean mouseMoved(int screenX, int screenY) {
            return false;
        }
        @Override
        public boolean scrolled(int amount) {
            return false;
        }

    Еще нам понадобятся несколько новых объектов - два материала для обработки выделения объекта и две переменных типа int, которых будем хранить индекс в массиве выделяемого и выделенного экземпляра модели. Объявим их и инициализируем. А также перенесем объявление вектора хранящего центр 3D-объекта Vector3 position из метода IsVisible в глобальную область класса, т.к. нам нужно будет использовать его в других методах.

        private Material selectMaterial, originMaterial;
        private int selected = -1, selecting = -1;
        Vector3 position = new Vector3();
                               
        @Override
        public void create () {
            ......
            selectMaterial = new Material(ColorAttribute.createDiffuse(Color.PINK));
            originMaterial = new Material();
            .....
       }

    Переменные selected и selecting инициализированы значениями -1. Это значит, что в данный момент ни один объект не выбран и не выбирается.

    Для наглядности добавим в информационную строку значение индекса выбранного объекта:

        public void render () {
            .....
            stringBuilder.append(" Visible: ").append(visibleModels);
            stringBuilder.append(" Selected: ").append(selected);
            label.setText(stringBuilder);
            stage.draw();
        }

    Так как у нас теперь два обработчика событий ввода (один для камеры), создадим мультиплексор и передадим его в метод Gdx.input.setInputProcessor.

       public void create () {
            ......
            camController = new CameraInputController(cam);
            Gdx.input.setInputProcessor(new InputMultiplexer(this, camController));
            ......
       }

    Причем обработчики ввода нужно передавать в мультиплексор именно в таком порядке - событие сначала передается в первый обработчик, если возвращается false, то оно передается во второй обработчик. Таким образом обработчик стоящий первым в параметрах мультиплексора имеет больший приоритет.

    Поскольку мы будем обрабатывать события мыши и касания, переопределим методы touchDown, touchUp и touchDragged.

        @Override
        public boolean touchDown(int screenX, int screenY, int pointer, int button) {
            selecting = getObject(screenX, screenY);
            return selecting >= 0;
        }
        @Override
        public boolean touchUp(int screenX, int screenY, int pointer, int button) {
            if(selecting >= 0){
                if(selecting == getObject(screenX, screenY)){
                    setSelected(selecting);
                    selecting = -1;
                }
            }
            return false;
        }
        @Override
        public boolean touchDragged(int screenX, int screenY, int pointer) {
            return selecting >= 0;
        }

    Здесь используются два метода, которые мы напишем чуть ниже: GetObject - возвратит индекс экземпляра модели в массиве, на который произошло касание/клик и setSelected - выделит выбранный объект.

    Что же здесь происходит? При касании/нажатии кнопки передаем в метод GetObject координаты касания. Если в этой точке есть объект, он вернет его индекс. Либо вернет -1 в случае отсутствия в этой точке объекта. Записываем полученное значений в переменную selecting. Возвращаем true, если объект есть в точке касания. Если нет возвращаем false и передаем полномочия следующему обработчику.

    При отпускании пальца/кнопки проверяем было ли касание на объект или нет. Если было проверяем находится ли палец/курсор все еще на этом объекте. При выполнении всех условий вызываем метод выделения объекта и возвращаем переменную selecting в состояние "объект не выбирается".

    Метод перемещения пока сделаем заглушкой и вернем true, если объект выбирается, либо передаем событие следующему обработчику.

    Напишем метод setSelected.

        public void setSelected(int value){
            if(selected == value)return;
            if(selected >= 0){
                Material temp = forest.get(selected).materials.get(0);
                temp.clear();
                temp.set(originMaterial);
            }
            selected = value;
            if(selected >= 0){
                Material temp = forest.get(selected).materials.get(0);
                originMaterial.clear();
                originMaterial.set(temp);
                temp.clear();
                temp.set(selectMaterial);
                
            }
        }

    В качестве параметра он получает индекс объекта, на который был клик/касание. В случае если этот объект уже выбран просто выходим из метода. Далее проверяем был ли до этого выделен другой объект, если да - получаем материал, который сейчас находится на объекте, очищаем его и устанавливаем родной материал, т.е. снимаем выделение. Затем присваиваем переменной selected переданный в метод индекс выбранного экземпляра модели. Если индекс не равен -1, сохраняем текущий материал в originMaterial и устанавливаем на объект selectedMaterial, т.е. выделяем его.

    Теперь напишем метод GetObject, возвращающий индекс объекта на который произошло касание/клик. В нем содержатся главные новшества нашего кода.

        public int getObject(int ScrX, int ScrY){
            Ray ray = cam.getPickRay(ScrX, ScrY);
            
            int res = -1;
            float span = -1f;
            for(int i = 0; i < forest.size; ++i){
                final ModelInstanceAdvanced inst = forest.get(i);
                inst.transform.getTranslation(position);
                position.add(inst.center);
                float span2 = ray.origin.dst2(position);
                if(span >= 0 && span2 > span)continue;
                if(Intersector.intersectRaySphere(ray, position, inst.radius, null)){
                    res = i;
                    span = span2;
                }
            }
            return res;
        }

    Сначала получим Ray. Ray - это луч, исходящий из точки касания и проходящий через все возможные точки 3D-пространства в выбранном направлении. Он характеризуется начальной точкой origin и направлением direction. Для определения выбранного объекта необходимо найти объект который пересекается этим лучом. Но в зависимости от угла обзора камеры луч может пересекать несколько объектов, поэтому нам нужно выбрать какой из этих объектов выбрать. Мы будем отбирать ближайший к камере.

    Объявим две переменные: res - для хранения индекса объекта и span - для хранения квадрата расстояния от камеры до этого объекта. Затем начинаем перебирать экземпляры моделей в массиве. Получаем центр экземпляра. Вычисляем квадрат расстояния от начала луча до центра объекта и записываем в переменную span2. Почему мы используем квадрат расстояния, а не фактическое расстояние? По одной простой причине - вычисление квадрата расстояния происходит быстрее (для вычисления используется формула нахождения гипотенузы и, чтобы вычислить фактическое расстояние, нужно будет использовать дополнительное действие - извлечение корня). Если расстояние до текущего объекта больше, чем последнее сохраненное расстояние в переменной span, то переходим к следующей итерации цикла, т.к. этот объект нам не подходит. Иначе проверяем пересекает ли луч этот объект методом Intersector.intersectRaySphere. Заметьте, что здесь мы снова используем границу сферы, которую мы рассматривали в предыдущей статье. Если пересечение есть записываем индекс в res, а квадрат расстояния в span. После окончания цикла в переменной res будет записан индекс ближайшего к камере объекта пересекаемого лучом. Возвращаем его в вызвавший метод.

    Запускаем программу:

    Деревья выделяются. Если внимательно посмотреть, то выделяются только часть объекта (крона). Это происходит потому, что на моделях используется два материала. А мы заменяем только один из них (попавший на модель при построении последним). Если хочется чтобы выделялся весь объект, нужно позаботится о замене всех материалов содержащихся на модели. Теперь сделаем так, чтобы выделенный объект можно было перемещать. Для этого переопределим метод touchDragged.

        @Override
        public boolean touchDragged(int screenX, int screenY, int pointer) {
            if(selecting < 0)return false;
            if(selected == selecting){
                Ray ray = cam.getPickRay(screenX, screenY);
                final float span = -ray.origin.y/ray.direction.y;
                position.set(ray.direction).scl(span).add(ray.origin);
                forest.get(selected).transform.setTranslation(position);
            }
            return true;
        }

    Если в точке касания нет объекта, передаем управление следующему обработчику. Затем проверяем было ли касание на выделенный объект. Если да, вычисляем луч.

    Теперь нам нужно определить расстояние от камеры до нового положения объекта. Поскольку перемещать нам необходимо только в плоскости XZ, то можем принять y = 0. Исходя из формулы y = ray.origin.y  + span * ray.direction.y (вспоминаем геометрию), подставляем вместо y ноль, преобразуем формулу и получаем расстояние span = -ray.origin.y/ray.direction.y

    Теперь зная расстояние до нового положения, можем вычислить координаты точки на луче куда следует переместить объект по формуле direction.(x,y,z) * span + origin.(x,y,z). Самим вычислять не обязательно - за нас это сделает вот такая комбинация методов:

    position.set(ray.direction).scl(span).add(ray.origin);

    Затем помещаем объект в полученную позицию.

    Все работает. Конечно не обязательно ограничивать перемещение именно плоскостью XZ, можно использовать любую плоскость, но не забывайте в таких случаях изменять формулы в соответствии с используемой плоскостью.

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

    public int getObject(int ScrX, int ScrY){
            Ray ray = cam.getPickRay(ScrX, ScrY);
            
            int res = -1;
            float span = -1f;
            for(int i = 0; i < forest.size; ++i){
                final ModelInstanceAdvanced inst = forest.get(i);
                inst.transform.getTranslation(position);
                position.add(inst.center);
                
                final float len = ray.direction.dot(position.x - ray.origin.x, position.y - ray.origin.y, position.z - ray.origin.z);
                if (len < 0f) continue;
                float span2 = position.dst2(ray.origin.x + ray.direction.x*len, ray.origin.y + ray.direction.y*len, ray.origin.z + ray.direction.z*len);
                if (span >= 0f && span2 > span) continue;
                if (span2 <= inst.radius * inst.radius) {
                    res = i;
                    span = span2;
                }
            }
            return res;
        }

    В метод ray.direction.dot передаем длины проекций на оси расстояния от начала луча до центра объекта, т.е. фактически мы передаем это расстояние в разобранном по осям виде. Этот метод возвращает длину отрезка луча от начала луча до точки куда опускается перпендикуляр. Далее методом position.dst2 вычисляем квадрат расстояния от центра объекта до точки пересечения перпендикуляра с лучом - span. Координаты последней передаем в параметрах. Формула по которой мы их вычисляем нам уже знакома (читаем выше). Далее сравниваем полученный квадрат расстояния с предыдущим значением, если больше переходим к следующей итерации. Затем сравниваем его с квадратом радиуса сферы объекта. В случае выполнения всех условий записываем индекс в res.

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

    Запустив приложение, видим проблема выбора дальних объектов устранена.

    Как эта программа будет выглядеть на Android-девайсе - небольшое видео:

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

    Полный код главного класса (проверка по длине перпендикуляра)

    import java.util.Random;
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.InputMultiplexer;
    import com.badlogic.gdx.InputProcessor;
    import com.badlogic.gdx.graphics.Camera;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.PerspectiveCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.VertexAttributes.Usage;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    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.attributes.TextureAttribute;
    import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
    import com.badlogic.gdx.graphics.g3d.model.Node;
    import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
    import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
    import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.math.collision.BoundingBox;
    import com.badlogic.gdx.math.collision.Ray;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Label;
    import com.badlogic.gdx.utils.Array;
    public class Proba3D extends ApplicationAdapter implements InputProcessor {
        
        public static class ModelInstanceAdvanced extends ModelInstance{
            public final Vector3 center = new Vector3();
            public final Vector3 dimens = new Vector3();
            public final float radius;
            private final static BoundingBox bound = new  BoundingBox();
            public ModelInstanceAdvanced(Model model) {
                super(model);
                calculateBoundingBox(bound);
                bound.getCenter(center);
                bound.getDimensions(dimens);
                radius = dimens.len()/2f;
            }
        }
        
        public PerspectiveCamera cam;
        public Model pineTree, LeafTree, ground;
        public ModelInstance groundInstance;
        public Array<ModelInstanceAdvanced> forest = new Array<ModelInstanceAdvanced>();
        public ModelBatch modelBatch;
        public Environment environment;
        public CameraInputController camController;
        public Material pine, trunk, land, crown, skin;
        
        private Material selectMaterial, originMaterial;
        private int selected = -1, selecting = -1;
        Vector3 position = new Vector3();
        
        protected Stage stage;
        protected Label label;
        protected BitmapFont font;
        protected StringBuilder stringBuilder;
        private int visibleModels;
                                
        @Override
        public void create () {
            stage = new Stage();
            font = new BitmapFont();
            label = new Label(" ", new Label.LabelStyle(font, Color.WHITE));
            stage.addActor(label);
            stringBuilder = new StringBuilder();
            
            selectMaterial = new Material(ColorAttribute.createDiffuse(Color.PINK));
            originMaterial = new Material();
            
            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(new InputMultiplexer(this, camController));
            
            pine = new Material(ColorAttribute.createDiffuse(Color.GREEN));
            crown = new Material(ColorAttribute.createDiffuse(Color.OLIVE));
            trunk = new Material(ColorAttribute.createDiffuse(Color.GRAY));
            TextureAttribute land_attr = TextureAttribute.createDiffuse(new Texture("land.jpg"));
            land = new Material(land_attr);
            skin = new Material(ColorAttribute.createDiffuse(Color.PINK));
            
            ModelBuilder modelBuilder = new ModelBuilder();
            modelBuilder.begin();
            Node node1 = modelBuilder.node();
            node1.id = "node1";
            node1.translation.set(0f, 2.8f, 0f);
            MeshPartBuilder meshBuilder;
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, pine);
            meshBuilder.cone(1f, 1f, 1f, 20);
            Node node2 = modelBuilder.node();
            node2.id = "node2";
            node2.translation.set(0f, 2f, 0f);
            meshBuilder = modelBuilder.part("part2", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, pine);
            meshBuilder.cone(2f, 1.5f, 2f, 20);
            Node node3 = modelBuilder.node();
            node3.id = "node3";
            node3.translation.set(0f, 1f, 0f);
            meshBuilder = modelBuilder.part("part3", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, pine);
            meshBuilder.cone(3f, 2f, 3f, 20);
            Node node4 = modelBuilder.node();
            node4.id = "node4";
            meshBuilder = modelBuilder.part("part4", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, trunk);
            meshBuilder.cylinder(1f, 1f, 1f, 20);
            pineTree = modelBuilder.end();
            
            modelBuilder.begin();
            Node lnode1 = modelBuilder.node();
            lnode1.id = "lnode1";
            lnode1.translation.set(0f, 1f, 0f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode2 = modelBuilder.node();
            lnode2.id = "lnode2";
            lnode2.translation.set(0.3f, 1.7f, 0f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode3 = modelBuilder.node();
            lnode3.id = "lnode3";
            lnode3.translation.set(-0.3f, 1.7f, 0.3f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode4 = modelBuilder.node();
            lnode4.id = "lnode4";
            lnode4.translation.set(-0.3f, 1.7f, -0.3f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode5 = modelBuilder.node();
            lnode5.id = "lnode5";
            lnode5.translation.set(0f, 2.2f, 0f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode6 = modelBuilder.node();
            lnode6.id = "lnode6";
            meshBuilder = modelBuilder.part("part4", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, trunk);
            meshBuilder.cylinder(0.7f, 1f, 0.7f, 20);
            LeafTree = modelBuilder.end();
            
            Random rnd = new Random();
            for(float x = -25f; x < 25f; x +=5f){
                for(float z = -25f; z < 25f; z +=5f){
                    if(rnd.nextInt(10) > 2){
                        float x_offset = 4*(0.5f - rnd.nextFloat());
                        float z_offset = 4*(0.5f - rnd.nextFloat());
                        ModelInstanceAdvanced tree;
                        if(rnd.nextInt(2) == 0){
                            tree = new ModelInstanceAdvanced(LeafTree);
                        }else{
                            tree = new ModelInstanceAdvanced(pineTree);
                        }
                        tree.transform.setToTranslation(x + x_offset, 0, z + z_offset );
                        forest.add(tree);
                    }
                }
            }
                        
            ground = modelBuilder.createBox(54f, 0.1f, 54f, land, Usage.Position|Usage.Normal|Usage.TextureCoordinates);
            groundInstance = new ModelInstance(ground);
            groundInstance.transform.setTranslation(0f, -0.5f, 0f);
        }
        @Override
        public void render () {
            camController.update();
            
            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);
            
            modelBatch.begin(cam);
            visibleModels = 0;
            
            for(final ModelInstanceAdvanced tree: forest){
                if(IsVisible(cam, tree)){
                    modelBatch.render(tree, environment);
                    visibleModels++;
                }
            }
            modelBatch.render(groundInstance, environment);
            visibleModels++;
            modelBatch.end();
            
            stringBuilder.setLength(0);
            stringBuilder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond());
            stringBuilder.append(" Visible: ").append(visibleModels);
            stringBuilder.append(" Selected: ").append(selected);
            label.setText(stringBuilder);
            stage.draw();
        }
        
        protected boolean IsVisible(final Camera cam, final ModelInstanceAdvanced instance) {
            instance.transform.getTranslation(position);
            position.add (instance.center);
            return cam.frustum.sphereInFrustum(position, instance.radius);
        }
        
        @Override
        public void resize(int width, int height) {
            stage.getViewport().update(width, height, true);
        }
        
        @Override
        public void dispose(){
            modelBatch.dispose();
            pineTree.dispose();
            LeafTree.dispose();
        }
        
        public int getObject(int ScrX, int ScrY){
            Ray ray = cam.getPickRay(ScrX, ScrY);
            
            int res = -1;
            float span = -1f;
            for(int i = 0; i < forest.size; ++i){
                final ModelInstanceAdvanced inst = forest.get(i);
                inst.transform.getTranslation(position);
                position.add(inst.center);
                
                final float len = ray.direction.dot(position.x - ray.origin.x, position.y - ray.origin.y, position.z - ray.origin.z);
                if (len < 0f)
                    continue;
         
                float span2 = position.dst2(ray.origin.x + ray.direction.x*len, ray.origin.y + ray.direction.y*len, ray.origin.z + ray.direction.z*len);
                if (span >= 0f && span2 > span) 
                    continue;
         
                if (span2 <= inst.radius * inst.radius) {
                    res = i;
                    span = span2;
                }
            }
            return res;
        }
        
        public void setSelected(int value){
            if(selected == value)return;
            if(selected >= 0){
                Material temp = forest.get(selected).materials.get(0);
                temp.clear();
                temp.set(originMaterial);
            }
            selected = value;
            if(selected >= 0){
                Material temp = forest.get(selected).materials.get(0);
                originMaterial.clear();
                originMaterial.set(temp);
                temp.clear();
                temp.set(selectMaterial);
                
            }
        }
        @Override
        public boolean keyDown(int keycode) {
            return false;
        }
        @Override
        public boolean keyUp(int keycode) {
            return false;
        }
        @Override
        public boolean keyTyped(char character) {
            return false;
        }
        @Override
        public boolean touchDown(int screenX, int screenY, int pointer, int button) {
            selecting = getObject(screenX, screenY);
            return selecting >= 0;
        }
        @Override
        public boolean touchUp(int screenX, int screenY, int pointer, int button) {
            if(selecting >= 0){
                if(selecting == getObject(screenX, screenY)){
                    setSelected(selecting);
                    selecting = -1;
                }
            }
            return false;
        }
        @Override
        public boolean touchDragged(int screenX, int screenY, int pointer) {
            if(selecting < 0)return false;
            if(selected == selecting){
                Ray ray = cam.getPickRay(screenX, screenY);
                final float span = -ray.origin.y/ray.direction.y;
                position.set(ray.direction).scl(span).add(ray.origin);
                forest.get(selected).transform.setTranslation(position);
            }
            return true;
        }
        @Override
        public boolean mouseMoved(int screenX, int screenY) {
            return false;
        }
        @Override
        public boolean scrolled(int amount) {
            return false;
        }
    }

    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 (17.04.2015)
    Просмотров: 1651 | Комментарии: 1
    | Теги: перетаскивание объектов, взаимодействие с объектами, touchDragged, 3D, libGDX | Рейтинг: 0.0/0
    Всего комментариев: 1
    1 wolf348   (08.07.2016 09:20)
    Подскажите. Что за геометрическая формула в методе  touchDragged, вот эта: "y = ray.origin.y  + span * ray.direction.y (вспоминаем геометрию)"?

    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Меню сайта
    Категории раздела
    Андроид разработка [23]
    libGDX [24]
    Мои андроид проекты [6]
    Excel [7]
    Железяки [5]
    Скрипты в блокноте [4]
    Разное [1]
    Форма входа
    Статистика

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