2D/3D моделирование в Android

Автор: | 26.09.2018

Загрузка картинок и пиксельное представление картинки.
2D графика методами объекта класса Canvas.
Простейшее OpenGL ES приложение.
3D графика на основе OpenGL ES.

Загрузка картинок и пиксельное представление картинки

Постановка задачи. Создать приложение, которое обеспечивает загрузку 3-х картинок (скачать картинки здесь). При нажатии (click) на нижнюю картинку вместо нее отображается точечное (пиксельное) представление картинки, которая расположена выше. Масштаб пиксельного отображения картинки задается в программе.

 

Исходная информация для выполнения задания:

Файл activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#AAF"
    android:padding="50dp" >

     <ImageView
        android:id="@+id/Image1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/im1"
        android:layout_marginTop="10dp"
        android:paddingLeft="90dp" />
    <ImageView
        android:id="@+id/Image2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/im2"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="90dp"/>

    <ImageView
        android:id="@+id/Image3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/im3"
        android:layout_marginTop="50dp"
        android:layout_marginLeft="40dp"
        android:onClick="sendMessage"/>

</LinearLayout>

Файл MainActivity.java

import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.graphics.Bitmap;
import android.widget.ImageView;
import android.graphics.Color;
import android.view.View;

public class MainActivity extends AppCompatActivity {


    //private int Wsrc;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // Обработка нажатия кнопки
    public void sendMessage(View view) {
        //ImageView secondImageView = (ImageView) findViewById(R.id.Image2);
        ImageView View3D = (ImageView) findViewById(R.id.Image3);
        ImageView View2D = (ImageView) findViewById(R.id.Image2);
        Bitmap src = ((BitmapDrawable)View2D.getDrawable()).getBitmap();
        Bitmap dest = Bitmap.createBitmap(600, 600, Bitmap.Config.ARGB_8888);
        // Закрашиваем фон и выводим точку красного цвета
        dest.eraseColor(Color.WHITE);

        int Mx = 2;
        int My = 2;

        for(int x = 0; x < src.getWidth(); x++){
            for(int y = 0; y < src.getHeight(); y++){
                // получим каждый пиксель
                int pixelColor = src.getPixel(x, y);
                // получим информацию о прозрачности
                int pixelAlpha = Color.alpha(pixelColor);
                // получим цвет каждого пикселя
                int pixelRed = Color.red(pixelColor);
                int pixelGreen = Color.green(pixelColor);
                int pixelBlue = Color.blue(pixelColor);
                // перемешаем цвета
                int newPixel= Color.argb(
                        pixelAlpha, pixelBlue, pixelRed, pixelGreen);
                // полученный результат вернём в Bitmap
                dest.setPixel(x, y, newPixel);
                int i = x * Mx;
                int j = y * My;
                dest.setPixel(i, j, newPixel);
            }
        }
        // Выведем результат в ImageView для просмотра
         View3D.setImageBitmap(dest);
    }
}

2D графика методами объекта класса Canvas

Создадим проект, в котором рисуется окружность:

Для вывода 2D-графики на экран в классе MainActivity вместо R.layout.activity_main  устанавливается вид (объект класса Draw2D), который формируется с помощью методов объекта класса Canvas.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        setContentView(new Draw2D(this));
    }
}

В классе Draw2D, производном от класса android.view.View, преопределяем метод onDraw(Canvas canvas), в котором реализуется вся работа с графикой. В качестве параметра этому методу передается объект класса Canvas, который реализует рисование.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class Draw2D extends View{
    Paint paint = new Paint();
    public Draw2D(Context context) {
        super(context);
    }
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        canvas.drawPaint(paint);
        paint.setAntiAlias(true);
        paint.setColor(Color.BLACK);
        canvas.drawCircle(250, 150, 50, paint);
    }
}

Архитектура приложения позволяет легко создавать динамическую графику путем  обновления (перерисовки) вида. Определим в классе Draw2D метод onTouchEvent(), добавим переменные touchX и touchX.

float touchX = 150;
float touchY = 30;
@Override
public boolean onTouchEvent(MotionEvent event)
{
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        touchX = event.getX();
        touchY = event.getY();
        invalidate(); }
    return true;
}

Также внесем изменения в функцию canvas.drawCircle:

canvas.drawCircle(touchX, touchY, 50, paint);

Кроме этого необходимо подключить библиотеку:

import android.view.MotionEvent;

Теперь при нажатии кнопки мышки окружность будет перемещаться в указанное курсором место.

Простейшее OpenGL ES приложение

Если Canvas не справляется, то больше ничего не остается, как использовать OpenGL. Для Андроид доступен OpenGL, но не такой как на десктопе, а урезанный. Причем, у нас даже есть выбор: OpenGL ES 1.0/1.1 (работает везде) или OpenGL ES 2.0 (Android 2.2+). Что такое ES в названии? ES говорит нам о том, что это урезанная версия OpenGL, из которой убрали все лишнее, чтобы сделать API как можно компактнее. Так как OpenGL ES 1.хх уже морально устарел, лучше сразу осваивать ES 2. Он проще и намного богаче в своих возможностях.

Ниже приводится код простейшего OpenGL ES приложения.

Файл MainActivity.java

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class MainActivity extends Activity {
       private GLSurfaceView mGLView;

       @Override
       protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             mGLView = new GLSurfaceView(this);
             mGLView.setRenderer(new ClearRenderer());
             setContentView(mGLView);
             //setContentView(R.layout.activity_main);
       }

       @Override
       protected void onPause() {
             super.onPause();
             mGLView.onPause();
             }
       @Override
       protected void onResume() {
             super.onResume();
             mGLView.onResume();
             }     
}

ClearRenderer.java

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView;

public class ClearRenderer implements GLSurfaceView.Renderer  {

       public void onSurfaceCreated(GL10 gl, EGLConfig config) {
             // Do nothing special.   
             }

       public void onSurfaceChanged(GL10 gl, int w, int h) {
                    gl.glViewport(0, 0, w, h);   
                    }
  
             public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);  
                    }

}

Здесь функция  setContentView в качестве аргумента вместо файла activity_main.xml принимает объект класса GLSurfaceView. Этот класс используется для написания OpenGL ES приложений в Android, с помощью него создается контекст воспроизведения OpenGL.

GLSurfaceView осуществляет настройку и использование OpenGL для Android относительно безболезненно. Он обеспечивает:

  • работу в отдельном потоке;
  • непрерывный рендеринг (обновление содержимого экрана);
  • настройки интерфейса между OpenGL и оконной системой.

3D графика на основе OpenGL ES

Приложение «Рупорная антенна» строится на основе алгоритма, изложенного в теме 3D графика на основе OpenGL WinApi C++.

 

Файл MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;


public class MainActivity extends AppCompatActivity {

    private Engine engine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        GLSurfaceView view = new GLSurfaceView(this);
        engine = new Engine(this);
        view.setRenderer(engine);
        setContentView(view);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        //getMenuInflater().inflate(R.layout, menu);
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: // нажатие
                engine.onTouchDown(x,y);
                break;
            case MotionEvent.ACTION_MOVE: // движение
                engine.onTouchMove(x,y);
                break;
            case MotionEvent.ACTION_UP:
                engine.onTouchDown(x,y);
                break;
            case MotionEvent.ACTION_CANCEL:
        }

        return true;
    }

Файл Engine.java

import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.FloatBuffer;
    import javax.microedition.khronos.egl.EGLConfig;
    import javax.microedition.khronos.opengles.GL10;
    import android.content.Context;
    import android.opengl.GLU;
    import android.opengl.GLSurfaceView.Renderer;
//import com.kco.scene.Game;
//import com.kco.scene.Textures;

    public class Engine implements Renderer {
        private FloatBuffer axes, model;
        private ByteBuffer indexes[] = new ByteBuffer[7];
        private int screenW, screenH;
        private float angleA,angleB;
        boolean touchState, firstMove;
        float lastX, lastY;

        public Engine(Context context) {
            //this.context = context;
            //Game.NewGame();
        }
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
            gl.glShadeModel(GL10.GL_SMOOTH);

            float axesVer[]={0,0,0, 1,0,0,  0,0,0, 0,1,0,  0,0,0, 0,0,1};
            ByteBuffer axesMem = ByteBuffer.allocateDirect(18*4);
            axesMem.order(ByteOrder.nativeOrder());
            axes = axesMem.asFloatBuffer();
            axes.put(axesVer);
            axes.position(0);

            float modelVer[]={0.4f,0.2f,0.0f,  -0.4f,0.2f,0.0f,   -0.4f,-0.2f,0.0f,   0.4f,-0.2f,0.0f,
                    0.2f,0.1f,-0.4f, -0.2f,0.1f,-0.4f,  -0.2f,-0.1f,-0.4f,  0.2f,-0.1f,-0.4f,
                    0.2f,0.1f,-0.8f, -0.2f,0.1f,-0.8f,  -0.2f,-0.1f,-0.8f,  0.2f,-0.1f,-0.8f};
            ByteBuffer modelMem = ByteBuffer.allocateDirect(12*3*4);
            modelMem.order(ByteOrder.nativeOrder());
            model = modelMem.asFloatBuffer();
            model.put(modelVer);
            model.position(0);
            byte indexesVer[][]={{0,1,2,3}, {4,5,6,7}, {8,9,10,11},
                    {0,4,8}, {1,5,9}, {2,6,10}, {3,7,11}};
            for (int i=0; i<3; i++) {
                indexes[i] = ByteBuffer.allocateDirect(4);
                indexes[i].put(indexesVer[i]);
                indexes[i].position(0);
            };
            for (int i=3; i<7; i++) {
                indexes[i] = ByteBuffer.allocateDirect(3);
                indexes[i].put(indexesVer[i]);
                indexes[i].position(0);
            };
        }
        public void onDrawFrame(GL10 gl) {
            gl.glClearColor(0.85f, 0.85f, 1.0f, 1.0f);
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            DrawAxes(gl);
            DrawModel(gl);
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        }
        public void DrawAxes(GL10 gl) {
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, axes);
            gl.glColor4f(1,0,0,0);
            gl.glDrawArrays(GL10.GL_LINES, 0, 2);
            gl.glColor4f(0,1,0,0);
            gl.glDrawArrays(GL10.GL_LINES, 2, 2);
            gl.glColor4f(0,0,1,0);
            gl.glDrawArrays(GL10.GL_LINES, 4, 2);
        }
        private void DrawModel(GL10 gl) {       // Отрисовка модели
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model);
            gl.glColor4f(0,0,0,0);
            gl.glPushMatrix();
            gl.glTranslatef(0.0f, 0.15f, 0.0f);
            gl.glRotatef(15-angleA, 0,1,0);
            gl.glRotatef(-45-angleB, 1,0,0);
            gl.glTranslatef(0.0f, 0.0f, 0.95f);
            for (int i=0; i<3; i++)
                gl.glDrawElements(GL10.GL_LINE_LOOP, 4, GL10.GL_UNSIGNED_BYTE, indexes[i]);
            for (int i=3; i<7; i++)
                gl.glDrawElements(GL10.GL_LINE_STRIP, 3, GL10.GL_UNSIGNED_BYTE, indexes[i]);
            gl.glScalef(0.5f, 0.5f, 0.5f);
            DrawAxes(gl);
            gl.glPopMatrix();
            //gl.glDrawArrays(GL10.GL_LINE_LOOP, 4, 4);
            //gl.glDrawArrays(GL10.GL_LINE_LOOP, 8, 4);
        };
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            //screenW=width;  screenH=height;

            gl.glViewport(0, 0, width, height);
            gl.glMatrixMode(GL10.GL_PROJECTION);   // Действия будут производиться с матрицей проекции
            gl.glLoadIdentity();
            //glTranslatef(0.0, 0.5, 0.0);
            //glFrustum(-1, 1, -1, 1, 3, 10);      // Устанавливается перспективная проекция
            GLU.gluPerspective(gl, 30, (float)width/(float)height, 0.1f, 10f);                // Устанавливается перспективная проекция
            //glOrtho(-1, 1, -1, 1, 3, 10);        // Устанавливается ортогональная проекция
            //gluOrtho2D(-10, 10, -10, 10);        // Устанавливается ортогональная проекция
            gl.glMatrixMode(GL10.GL_MODELVIEW);    // Действия будут производиться с матрицей модели
            gl.glLoadIdentity();
            GLU.gluLookAt(gl, 5, 5, 5, 0, 0, 0, 0, 1, 0);
        }
        public void onTouchDown(float x, float y) {
            lastX=x; lastY=y;
            touchState=true;
            firstMove=true;
        };
        public void onTouchUp(float x, float y) {
            touchState=false;
        };
        public void onTouchMove(float x, float y) {
            if (touchState==true)
                if (firstMove==false) {
                    angleA+=(lastX-x)*0.5f;
                    angleB+=(lastY-y)*0.5f;
                    angleA-=((int)angleA/360)*360;
                    angleB-=((int)angleB/360)*360;
                }
                else
                    firstMove=false;
            lastX=x;
            lastY=y;
        };
    };

Отличительной особенностью программной реализации изложенного в теме 3D графика на основе OpenGL WinApi C++ есть то, что в Android приложении использована современная  OpenGL технология, позволяющая хранить координаты вершин совместно с их атрибутами в массивах.

Раньше (см. 3D графика на основе OpenGL WinApi C++) для отрисовки антенны использовался такой код:

glBegin(GL_LINE_LOOP);
        // v0-v1-v2-v3
        glVertex3f(0.4,0.2,0);
        glVertex3f(-0.4,0.2,0);
        glVertex3f(-0.4,-0.2,0);
        glVertex3f(0.4,-0.2,0);
glEnd();

glBegin(GL_LINE_LOOP);     
        // v4-v5-v6-v7
        glVertex3f(0.2,0.1,-0.4);
        glVertex3f(-0.2,0.1,-0.4);
        …

В OpenGL ES 2 это уже не работает. Вместо определения параметров точек через вызовы кучи методов, мы определяем все с помощью массива вершин:

float modelVer[]={0.4f,0.2f,0.0f, -0.4f,0.2f,0.0f,
-0.4f,-0.2f,0.0f,   0.4f,-0.2f,0.0f,
 0.2f,0.1f,-0.4f, -0.2f,0.1f,-0.4f,
 -0.2f,-0.1f,-0.4f,  0.2f,-0.1f,-0.4f,
 0.2f,0.1f,-0.8f, -0.2f,0.1f,-0.8f,
-0.2f,-0.1f,-0.8f,  0.2f,-0.1f,-0.8f};

При этом порядок обхода вершин определяется в массиве индексов:

byte indexesVer[][]={{0,1,2,3}, {4,5,6,7}, {8,9,10,11},
               {0,4,8}, {1,5,9}, {2,6,10}, {3,7,11}};

Контрольное задание. В исходной программе используется проволочная модель (с использованием примитивов GL_LINE_LOOP и GL_LINE_STRIP). Поверх проволочной модели создайте еще и поверхностную модель с использованием примитива GL_TRIANGLE_STRIP.

Вершины в массиве индексов указываются в порядке, как показано на  примере для нижней грани антенны:

См. результат работы программы на рисунке:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *