Bitácora de Javier Cancela

Realidad aumentada en Android: AR Compass – III

La entrada anterior se centró la clase Compass, que describe la brújula. También presentaba la clase CompassRenderer, que es la encargada de dibujarla en su sitio. Vamos a ver esta clase con más detalle.

La clase CompassRenderer

   1: class CompassRenderer implements GLSurfaceView.Renderer, SensorEventListener {
   2:         private float   mAccelerometerValues[] = new float[3];
   3:         private float   mMagneticValues[] = new float[3];
   4:         private float rotationMatrix[] = new float[16];
   5:         private float remappedRotationMatrix[] = new float[16];
   6:
   7:         private Compass mCompass;
   8:
   9:     public CompassRenderer() {
  10:         mCompass = new Compass();
  11:     }

La clase CompassRenderer cumple dos funciones: capturar la información de los sensores y renderizar la brújula con esa información.

Implementación de la interfaz SensorEventListener

Como hemos visto en otras entradas, el método onSensorChanged es invocado cada vez que hay nueva información disponible de alguno de los sensores a los que nos hemos subscrito:

   1: @Override
   2: public void onSensorChanged(SensorEvent event) {
   3:        synchronized (this) {
   4:             switch(event.sensor.getType()) {
   5:             case Sensor.TYPE_ACCELEROMETER:
   6:                 mAccelerometerValues = event.values.clone();
   7:                 break;
   8:             case Sensor.TYPE_MAGNETIC_FIELD:
   9:                 mMagneticValues = event.values.clone();
  10:                 break;
  11:             default:
  12:                 break;
  13:             }
  14:        }
  15: }

Para obtener la matriz de rotación (como veremos después) sólo necesitamos el acelerómetro y el sensor de campo magnético.

Un punto a tener en cuenta en este caso es la sincronización. Al código de la clase CompassRenderer van a acceder dos threads: el principal y thread de OpenGL. Las llamadas a los métodos de los sensores vendrán del primero, mientras que las llamadas a los métodos de renderización vendrán del segundo. Esto quiere decir que las variables miembro mAccelerometerValues y mMagneticValues serán asignadas en un thread y leídas en el otro, por lo que usamos un lock para evitar que se lean mientras se están asignando.

Creación de la superficie OpenGL

   1: public void onSurfaceCreated(GL10 gl, EGLConfig config) {
   2:     /*
   3:      * By default, OpenGL enables features that improve quality
   4:      * but reduce performance. One might want to tweak that
   5:      * especially on software renderer.
   6:      */
   7:     gl.glDisable(GL10.GL_DITHER);
   8:
   9:     /*
  10:      * Some one-time OpenGL initialization can be made here
  11:      * probably based on features of this particular context
  12:      */
  13:      gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
  14:              GL10.GL_FASTEST);
  15:
  16:      gl.glClearColor(0,0,0,0);
  17: }

Cuando el GLThread crea la superficie ejecuta el método onSurfaceCreated. Las líneas 7 y 13 son optimizaciones que le indican a OpenGL que optimice el rendimiento sobre la calidad de la imagen. La línea 16 establece los valores que se utilizarán para limpiar el buffer de color.

   1: public void onSurfaceChanged(GL10 gl, int width, int height) {
   2:      gl.glViewport(0, 0, width, height);
   3:
   4:      /*
   5:       * Set our projection matrix. This doesn't have to be done
   6:       * each time we draw, but usually a new projection needs to
   7:       * be set when the viewport is resized.
   8:       */
   9:      float ratio = (float) width / height;
  10:      gl.glMatrixMode(GL10.GL_PROJECTION);
  11:      gl.glLoadIdentity();
  12:      gl.glFrustumf(-ratio, ratio, -1, 1, 1, 100);
  13: }

El método onSurfaceChanged después de creada la superficie, y adicionalmente cada vez que esta cambia de tamaño. En la línea 2 establecemos el viewport. Básicamente le decimos a sistema que utilice todo el alto y ancho de la pantalla para pintar el modelo. Las líneas 9-12 inicializan la matriz de proyección y establece la región  que se va a visualizar (ver por ejemplo este tutorial).

Dibujo de la brújula

Cada cuadro correspondiente a la animación de la brújula se dibuja en el método onDrawFrame:

   1: public void onDrawFrame(GL10 gl) {
   2:     // Get rotation matrix from the sensor
   3:     SensorManager.getRotationMatrix(rotationMatrix, null, mAccelerometerValues,
                                          mMagneticValues);
   4:     // As the documentation says, we are using the device as a compass in landscape 
          // mode
   5:     SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_Y,
                                              SensorManager.AXIS_MINUS_X,
                                              remappedRotationMatrix);
   6:
   7:     // Clear color buffer
   8:     gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
   9:
  10:     // Load remapped matrix
  11:     gl.glMatrixMode(GL10.GL_MODELVIEW);
  12:     gl.glLoadIdentity();
  13:     gl.glLoadMatrixf(remappedRotationMatrix, 0);
  14:
  15:     gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  16:     gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
  17:
  18:     mCompass.draw(gl);
  19: }

En la línea 3 obtenemos la matriz de rotación a partir de los sensores. Como vamos a ver la imagen en formato horizontal giramos los ejes en la línea 5. Las líneas 11-13 cargan la matriz. Después habilitamos los arrays de vértices y colores en el cliente que como vimos usa la clase Compass, y finalmente llamamos al método de dibujo de la brújula (visto en la entrada anterior).

El código completo del proyecto está disponible aquí: ARCompass.

About these ads

Escrito por Javier Cancela

17 \17\UTC agosto \17\UTC 2009 a 7:00

11 comentarios

Suscríbete a los comentarios mediante RSS.

  1. [...] here to read the rest:  Realidad aumentada en Android: AR Compass – III var AdBrite_Title_Color = '0000FF'; var AdBrite_Text_Color = '000000'; var [...]

  2. Hola Javier,

    antes de nada felicitarte por tu blog. Nos estás ayudando mucho a a más de uno. Verás, te escribo porque estoy llevando a cabo un PFC sobre Realidad Aumentada. Aún estoy un poco verde en lo que a Android se refiere. Tu ARCompass me viene genial. El problema está en que aún no tengo el teléfono Android físico, me apaño con el emulador. Al tratar de ejecutar el ARCompass en el AVD no me sale la brújula (sí la imagen de la cámara). He estado mirando qué podría ser y curiosamente la instrucción (mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD)).size() devuelve 0. Como si el AVD no tuviera magnetómetro. Por favor, ¿me puedes echar un cable?

    Muchas gracias.

    Alfonso

    12 \12\UTC noviembre \12\UTC 2009 a 3:58

    • Bueno, he dado con la respuesta, así que por si le sirve a álguien aquí lo dejo: El ejemplo hecho por Javier Cancela se basa en Android 1.5 y el Sensor Simulator está pensado para versiones anteriores de Android. Es por ello que el simulador no usa la interfaz SensorEventListener sino SensorListener, calificado como deprecated (despreciado).

      Alfonso

      17 \17\UTC noviembre \17\UTC 2009 a 14:49

  3. Hola Javier, he tomado tu ARCompass y le he hecho algunas modificaciones. Ya he visto que tu blog está bajo licencia Creative Commons. Quería preguntarte si hay algún problema en que incluya la versión modificada de tu aplicación en la memoria de mi Proyecto de Fin de Carrera. Por supuesto mencionando que tú eres el autor y por descontado que mi Proyecto de Fin de Carrera no tiene ánimo de lucro ninguno (Ojalá!!!).
    Lo que no sé es si el PFC puede enmarcarse dentro de una licencia idéntica a la que tú tienes para tu blog. En cualquier caso la versión modificada de tu aplicación es sólo un prototipo que me ayuda a explicar las bases de cómo manejar los sensores, OpenGL, las vistas, etc. La aplicación cuyo desarrollo es el objetivo del proyecto la estoy desarrollando desde cero.
    A todo esto, si quieres te paso la versión modificada y la incluyes en tu blog, que no tengo problema ninguno. ¿Me das tu permiso para incluir la versión modificada en mi PFC?
    Tanto si la respuesta es afirmativa como en caso contrario (por supuesto que no me enfado contigo, aunque tuviera que hacerme otro prototipo desde cero este es obra tuya) te ruego te pongas en contacto conmigo por e-mail.

    Muchas gracias.

    Alfonso

    28 \28\UTC enero \28\UTC 2010 a 5:12

  4. Javier, me encantaría disponer de más tiempo, sin embargo tengo que ir liquidando esto. Sé que hace sólo unos pocos días que te pedí que contactaras conmigo, pero (como buen ingeniero) no dispongo de mucho tiempo.

    Resumiendo, aún no tengo respuesta tuya así que estoy elaborando un prototipo propio desde cero. No incluiré nada de código de tu prototipo en mi proyecto de fin de carrera. No obstante, puedes estar seguro de que te mencionaré como un punto de referencia en esto de la programación de Realidad Aumentada.

    Si aún así quisieras intercambiar algún e-mail conmigo, estaría encantado.

    Un saludo.

    Alfonso

    1 \01\UTC febrero \01\UTC 2010 a 18:37

  5. Hola Javier.

    Gracias a tu trabajo he podido dar un avance gigante en mi tesis de maestría que evidentemente usa realidad aumentada para la interacción con el usuario.

    Al igual que Alfonso tengo que consultarte sobre la publicación del código pues la parte de la obtención de la matriz de rotación y el aprendizaje de OpenGL ES fue gracias al trabajo que publicaste.

    Por lo pronto sólo puedo decir que estaré eternamente agradecido contigo ya que gracias a la publicación de tu trabajo nos sacaste a varios de nuestros líos académicos. Espero tu respuesta y evidentemente seguiré tu ejemplo para seguir aportando a la comunidad de desarrolladores Android publicando lo que he hecho en mi blog, un saludo desde Bogotá, Colombia.

    Atentamente,
    Carlos Omaña

    Carlos Omaña

    10 \10\UTC febrero \10\UTC 2010 a 15:43

    • Hola Carlos.

      Puedes utilizar todo lo que encuentres en este blog como desees. Como le dije a Alfonso por correo, nada más satisfactorio que encontrar a gente a la que le resulte útil lo que escribo por aquí.

      Un saludo.

      Javier Cancela

      10 \10\UTC febrero \10\UTC 2010 a 20:20

  6. Hola Javier,

    Estoy trabajando en una Tesis de Grado para Ing. Informatica basada en Realidad Aumentada sobre Android y gracias a tu blog he obtenido muchisima informacion realmente util. Muchisimas gracias.

    Actualmente tengo un par de dudas y tu quizas podrias ayudarme a resolverlas.

    A partir de la latitud, longitud y altura, es posible calcular las coordenadas x,y,z en un sistema de coordenadas cuyo origen es el centro de la tierra. Existen varios algoritmos que convierten de coordenadas geodesicas a cartesianas.

    Lo que no termino de entender bien, es luego como llevar esas coordenadas al “Sistema de Coordenadas del Mundo” definido para la funcion getRotationMatrix.

    No estoy utilizando OpenGL por lo que necesito poder posicionar ciertos puntos con respecto al “World Coordinate System”.

    Espero puedas ayudarme.

    Saludos

    Ricardo Recaredo

    17 \17\UTC marzo \17\UTC 2010 a 5:50

    • Estimado, estoy en el mismo punto que tú. Me gustaría saber si has logrado algo nuevo con eso y si puedes ayudarme al respecto.

      Una forma que estoy barajando es a través del ángulo con respecto al norte (de mi posición al POI), y ese ángulo interpretarlo de manera similar a como Javier presenta este tutorial, que de paso agradezco el documento, ya que me ha ayudado bastante a entender cómo lograr AR con OpenGL

      Carlos

      1 \01\UTC julio \01\UTC 2010 a 0:22

  7. Graciasssss!!!!

    Gustavo

    6 \06\UTC agosto \06\UTC 2010 a 13:16

  8. Para conseguir que me funcionará en Android 2.2 he tenido que añadir esta línea: getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

    Después de línea:
    requestWindowFeature(Window.FEATURE_NO_TITLE);

    Por otro lado, es recomendable poner un “try catch” en la función “surfaceChanged” para que no de errores “dramáticos”.

    Saludos!

    Marta

    10 \10\UTC diciembre \10\UTC 2010 a 8:59


Los comentarios están cerrados.

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

%d bloggers like this: