En este video te explicamos en detalle todo el proceso para publicar una Aplicación Android…
Proyecto Android con OpenGL: iluminación e interacción con objetos 3D
3.
Proyecto Android con OpenGL: iluminación e interacción con objetos 3D
En este tutorial vamos a desarrollar un proyecto Android, donde a partir de objetos 3D representados, se establecerán los parámetros de iluminación, además de implementar eventos de interacción con el usuario cómo la pantalla táctil o el acelerómetro. Para este ejemplo, sólo se hará hincapié en las modificaciones que hagamos respecto al primer caso práctico que publicamos sobre OpenGL.
Puedes descargar todo el código del proyecto, al final de este tutorial
En la Aplicación Android que vamos a crear, se definirá un menú principal, en el que podremos seleccionar la opción «Interacción usuario con objetos 3D», mostrando posteriormente los objetos cubo y pirámide en la misma vista.
Elementos necesarios:
- Una clase principal llamada MainActivity, que heredará de la clase base Activity, donde se definen los elementos enlazados con sus recursos a nivel de layout, además desde esta Activity se realiza la llamada a una nueva View, encargada de construir los objetos gráficos (sólo nos centraremos en la opción «Interacción usuario con objetos 3D» del componente RadioButton definido):
- Una clase llamada GLRender, que implementará la interfaz Renderer, encargada de construir la vista dónde se representará el objeto, además de definir los parámetros de iluminación y posición de las luces.
- Una clase llamada Cubo3D y Piramide3D, dónde se definen los parámetros del objeto 3D que será invocado desde la clase GLRender.
- Una clase llamada HiloEventosUsuario, encargada de controlar las acciones que realice el usuario al tocar la pantalla del dispositivo. Esta clase hereda de la clase base GLSurfaceView.
En la imagen vemos que como se muestran los objetos 3D en la vista. El usuario podrá manejar la rotación de ambos, mediante el sensor de la pantalla táctil del dispositivo, pudiendo observar los cambios de iluminación de las caras de los objetos.
FormasPrimitivas/src/com.academiaandroid.formasprimitivas/HiloEventosUsuario.java
Vamos a ver paso a paso el desarrollo de este proyecto:
Se crea una nueva clase llamada HiloEventosUsuario, encargada de controlar las acciones que realice el usuario al tocar la pantalla del dispositivo. Esta clase hereda de la clase base GLSurfaceView, y su principal tarea es implementar el evento onTouchEvent(final MotionEvent event) , que cazará las pulsaciones en la pantalla del usuario, invocando el método girarObjetos() de la clase GLRender:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
//Clase que hereda de la clase base GLSurfaceView, desde la que controlaremos //los cambios que se realicen sobre la pantalla táctil. public class HiloEventosUsuario extends GLSurfaceView [...] //Evento encargado de controlar las acciones que se realicen al tocar la pantalla, encargado de lanzar un nuevo hilo para invocar al método girarObjetos de la clase GLRender. @Override public boolean onTouchEvent(final MotionEvent event) { if( event != null ){ queueEvent(new Runnable() { @Override public void run() { render.girarObjetos(event); } }); return true; } return super.onTouchEvent( event ); } |
FormasPrimitivas/src/com.academiaandroid.formasprimitivas/GLRender.java
En primer lugar definimos e inicializamos las variables, a nivel de clase, necesarias para establecer la iluminación en la escena representada. También se definen tres variables de tipo float, que almacenarán las coordenadas de la posición de la pantalla que seleccione el usuario:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Variable para encender o no la iluminación en la escena. boolean encenderFoco = true; //Variables para controlar el tipo de iluminación y la posición del foco //en la escena. private float[] luzAmbiente = {0.2f, 0.2f, 0.2f, 1.0f}; private float[] luzDifusa = {3.0f, 3.0f, 3.0f, 3.0f}; private float[] luzPosicion = {1.0f, 1.0f, 2.0f, 1.0f}; //Variables de tipo float para controlar el ángulo de rotación de las //figuras representadas. public float anguloX, anguloY, anguloZ; |
Se sobrescribe el método onSurfaceCreated() con la configuración de la iluminación que se define en la vista construida:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//Método que será invocado una vez para configurar la vista. @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { //Habilitamos la iluminación. gl.glEnable(GL10.GL_LIGHTING); //Activamos la primera luz individualmente. gl.glEnable(GL10.GL_LIGHT0); //Introducimos las características de la luz: luz ambiente, luz //difusa y posición. gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, luzAmbiente,0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, luzDifusa,0); gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, luzPosicion,0);//Si //el último parámetro es 1, la luz se sitúa en las //coordenadas indicadas en los tres primeros, si es 0 se sitúa en el infinito. //Por último, especificamos el modo en que OpenGL realizará el //sombreado sobre el objeto. gl.glShadeModel(GL10.GL_SMOOTH); } [...] |
Se habilita la iluminación al representar la vista inicialmente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
//Método que permite construir los diferentes objetos gráficos dentro de la vista definida. public void seleccionForma(int flag, GL10 gl) { [...] //Al inicializar la variable encenderFoco en true, siempre estará habilitado el foco definido. if (encenderFoco) { gl.glEnable(GL10.GL_LIGHTING); } else { gl.glDisable(GL10.GL_LIGHTING); } [...] //Parámetros de rotación, traslación y escalado del objeto cubo. //Permite establecer el movimiento de translación indicando las coordenadas del eje X, Y y Z. gl.glTranslatef(-3.0f, 0.0f, -5.0f); //Permite establecer el tamaño de la figura indicando las coordenadas del eje X, Y y Z. gl.glScalef(0.8f, 0.8f, 0.8f); //Rotación del objeto cubo a través del sensor de la pantalla táctil. gl.glRotatef(anguloX, 1.0f, 1.0f, 1.0f); gl.glRotatef(anguloY, 1.0f, 1.0f, 1.0f); gl.glRotatef(anguloZ, 1.0f, 1.0f, 1.0f); //Invocamos el método del objeto cubo3D encargado de establecer los parámetros del gráfico a dibujar. cubo3D.draw(gl); //Parámetros de rotación, traslación y escalado del objeto pirámide. gl.glScalef(0.8f, 0.8f, 0.8f); gl.glTranslatef(-3.0f, 0.0f, -5.0f); //Rotación del objeto pirámide a través del sensor de la pantalla táctil. gl.glRotatef(anguloX, 1.0f, 1.0f, 1.0f); gl.glRotatef(anguloY, 1.0f, 1.0f, 1.0f); gl.glRotatef(anguloZ, 1.0f, 1.0f, 1.0f); //Invocamos el método del objeto piramide3D encargado de establecer los parámetros del gráfico a dibujar. piramide3D.draw(gl); [...] |
Se define el método que será invocado por el evento que controla las diferentes acciones que se realicen sobre la pantalla. Los valores del eje X e Y serán enviados al primer parámetro del método glRotatef() para actualizar la posición de rotación del objeto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//Método que será invocado por el evento encargado de controlar los cambios que se produzcan en el sensor de la pantalla táctil. Recibe como argumento la clase <strong>MotionEvent</strong>. public void girarObjetos(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: //Controla los miovimientos de la pantalla. anguloX = event.getX(); anguloY = event.getY(); Log.d ("DEBUG","ACTION_MOVE"); break; case MotionEvent.ACTION_DOWN: //Controla la pulsación de la pantalla. modificarAngulo(anguloX - event.getX(), anguloY - event.getY(),0.0f); anguloX = event.getX(); anguloY = event.getY(); Log.d("DEBUG","ACTION_DOWN"); break; case MotionEvent.ACTION_UP: //Controla cuando dejas de pulsar la pantalla. modificarAngulo(anguloX + event.getX(), anguloY + event.getY(),0.0f); anguloX = event.getX(); anguloY = event.getY(); Log.d("DEBUG","ACTION_UP"); break; default: break; } } |
FormasPrimitivas/src/com.academiaandroid.formasprimitivas/Cubo3D.java
Se definen e inicializan las variables a nivel de clase, necesarias para establecer la iluminación en la escena representada.
1 2 3 4 5 6 |
//Variables para controlar el tipo de iluminación del foco en la escena. private float[] luzAmbiente = {0.5f, 0.5f, 0.5f, 1.0f}; private float[] luzDifusa = {3.0f, 3.0f, 3.0f, 3.0f}; [...] |
Se habilita la definición de materiales, permitiendo diferenciar la forma en que la luz se refleja sobre los objetos:
1 2 3 4 5 6 7 8 9 |
//Método donde se definen los parámetros para pintar el cubo. public void draw(GL10 gl) { [...] //Se habilita la definición de materiales. gl.glEnable(GL10.GL_COLOR_MATERIAL); //Posteriormente se definen las propiedades de los materiales. gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_DIFFUSE, luzDifusa,0); gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_AMBIENT, luzAmbiente,0); |
FormasPrimitivas/src/com.academiaandroid.formasprimitivas/Piramide3D.java
Al igual que la clase cubo3D, se definen e inicializan las variables a nivel de clase, necesarias para establecer la iluminación en la escena representada:
1 2 3 4 5 6 |
//Variables para controlar el tipo de iluminación del foco en la escena. private float[] luzAmbiente = {0.5f, 0.5f, 0.5f, 1.0f}; private float[] luzDifusa = {3.0f, 3.0f, 3.0f, 3.0f}; [...] |
Y posteriormente, dentro del método draw(), se habilita la definición de materiales, permitiendo diferenciar la forma en que la luz se refleja sobre los objetos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//Método donde se definen los parámetros para pintar la pirámide. public void draw(GL10 gl) { [...] //Método donde se definen los parámetros para pintar la pirámide. public void draw(GL10 gl) { [...] //Se habilita la definición de materiales. gl.glEnable(GL10.GL_COLOR_MATERIAL); //Posteriormente se definen las propiedades de los materiales. gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_DIFFUSE, luzDifusa,0); gl.glMaterialfv(GL10.GL_FRONT, GL10.GL_AMBIENT, luzAmbiente,0); [...] |
FormasPrimitivas/res/layout/activity_main.xml
Se define un recurso de tipo string, para asociarlo a los componentes de tipo texto mostrado en el menú principal. También comentar, que el componente RadioButton «radioCubo3D», que mostrará el texto «Interacción usuario con objetos 3D», será la opción que nos permita controlar la rotación de objetos 3D en la pantalla, además de poder observar los diferentes cambios de iluminación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<TableLayout android:layout_width="match_parent" android:layout_height="match_parent" > [...] <TableRow android:id="@+id/tableRow5" android:layout_width="wrap_content" android:layout_height="wrap_content" > <RadioGroup android:id="@+id/radioGroupFormas3D" android:layout_width="wrap_content" android:layout_height="wrap_content" > <RadioButton android:id="@+id/radioCubo3D" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="@string/formas" /> <RadioButton android:id="@+id/radioPiramide3D" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/piramide" /> </RadioGroup> </TableRow> [...] </TableLayout> |
Recurso strings.xml:
FormasPrimitivas/res/values/strings.xml
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="formas">Interacción usuario con objetos 3D</string> <string name="cuadrado">Cuadrado</string> <string name="triangulo">Triángulo</string> <string name="piramide">Pirámide 3D</string> <string name="titulo2D">Seleccione forma 2D a pintar:</string> <string name="titulo3D">Seleccione forma 3D a pintar:</string> </resources> |