Rendering the cube in ARCubeRenderer

Android provides a class called GLSurfaceView, which is a widget that is drawn by OpenGL. The drawing logic is encapsulated via an interface called GLSurfaceView.Renderer, which we will implement in ARCubeRenderer. The interface requires the following methods:

The GL10 instance, which is passed as an argument, provides access to the standard OpenGL ES 1.0 functionality. Basically, we are interested in two kinds of OpenGL functionality; applying matrix transformations to 3D vertices and then drawing triangles based on the transformed vertices. Our cube will have eight vertices and 12 triangles (six square faces * two triangles per square face). We will specify a color for each vertex and we will describe the triangles in a format called a triangle fan . A triangle fan is an array of 3 or more vertices. For each vertex v[i] in the fan, where i >= 2, a triangle is formed by v[0], v[i-1], and v[i].

Taking any vertex in the cube, we may imagine six triangles (three square faces) fanning out from that vertex. Thus, two triangle fans are enough to specify the 12 triangles, provided that we start the fans from opposite corners of the cube.

Vertices, vertex colors, and triangle fans are all stored in ByteBuffer instances. Since we only support one style of cube, we will use static instances of ByteBuffer so that multiple ARCubeRenderer instances may share them. As member variables, we also want ARFilter to provide the cube's pose matrix, a CameraProjectionAdapter to provide the projection matrix, and a scale to allow client code to resize the cube. The declarations of ARCubeRenderer and its variables are as follows:

public class ARCubeRenderer implements GLSurfaceView.Renderer {

  public ARFilter filter;
  public CameraProjectionAdapter cameraProjectionAdapter;
  public float scale = 100f;

  private static final ByteBuffer VERTICES;
  private static final ByteBuffer COLORS;
  private static final ByteBuffer TRIANGLE_FAN_0;
  private static final ByteBuffer TRIANGLE_FAN_1;

Since the vertices, colors, and triangle fans are static variables, we initialize them in a static block. For each buffer, we must specify the required number of bytes. The vertices take up 96 bytes (8 vertices * 3 floats per vertex * 4 bytes per float). We specify vertices for a cube that is 2 units wide. After populating the buffer, we rewind its pointer to the first index. The code is as follows:

  static {
    VERTICES = ByteBuffer.allocateDirect(96);
    VERTICES.order(ByteOrder.nativeOrder());
    VERTICES.asFloatBuffer().put(new float[] {
      -1f,  1f,  1f,
       1f,  1f,  1f,
       1f, -1f,  1f,
      -1f, -1f,  1f,

      -1f,  1f, -1f,
       1f,  1f, -1f,
       1f, -1f, -1f,
      -1f, -1f, -1f
    });
    VERTICES.position(0);

The vertex colors take up 32 bytes (8 vertices * 4 bytes of RGBA color per vertex). We specify a different color for each vertex, as seen in the following code:

    COLORS = ByteBuffer.allocateDirect(32);
    COLORS.put(new byte[] {
      // yellow
      Byte.MAX_VALUE, Byte.MAX_VALUE, 0, Byte.MAX_VALUE,
      // cyan
      0, Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE,
      // black
      0, 0, 0, Byte.MAX_VALUE,
      // magenta
      Byte.MAX_VALUE, 0, Byte.MAX_VALUE, Byte.MAX_VALUE,

      Byte.MAX_VALUE, 0, 0, Byte.MAX_VALUE, // red
      0, Byte.MAX_VALUE, 0, Byte.MAX_VALUE, // green
      0, 0, Byte.MAX_VALUE, Byte.MAX_VALUE, // blue
      0, 0, 0, Byte.MAX_VALUE // black
    });
    COLORS.position(0);

The two triangle fans take up 18 bytes each (6 triangles * 3 vertex indices per triangle). We specify fans that are based at the cube's far upper-right and near lower-left corners, as seen in the following code:

    TRIANGLE_FAN_0 = ByteBuffer.allocate(18);
    TRIANGLE_FAN_0.put(new byte[] {
      1, 0, 3,
      1, 3, 2,
      1, 2, 6,
      1, 6, 5,
      1, 5, 4,
      1, 4, 0
    });
    TRIANGLE_FAN_0.position(0);

    TRIANGLE_FAN_1 = ByteBuffer.allocate(18);
    TRIANGLE_FAN_1.put(new byte[] {
      7, 4, 5,
      7, 5, 6,
      7, 6, 2,
      7, 2, 3,
      7, 3, 0,
      7, 0, 4
    });
    TRIANGLE_FAN_1.position(0);
  }

When drawing to an instance of GLSurfaceView, we first clear any previous content by replacing it with a fully transparent color. Then, we check whether a projection matrix and pose matrix are available. If they are, we tell OpenGL to use these matrices and to also move and scale the cube so that we have an appropriately sized cube sitting atop the target. Then, we supply the vertices and vertex colors to OpenGL and tell it to draw the triangle fans. The implementation is as follows:

  @Override
  public void onDrawFrame(final GL10 gl) {

    gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
      GL10.GL_DEPTH_BUFFER_BIT);
    gl.glClearColor(0f, 0f, 0f, 0f); // transparent

    if (filter == null) {
      return;
    }

    if (cameraProjectionAdapter == null) {
      return;
    }

    float[] pose = filter.getGLPose();
    if (pose == null) {
      return;
    }

    gl.glMatrixMode(GL10.GL_PROJECTION);
    float[] projection =
      cameraProjectionAdapter.getProjectionGL();
    gl.glLoadMatrixf(projection, 0);

    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadMatrixf(pose, 0);
    gl.glTranslatef(0f, 0f, 1f);
    gl.glScalef(scale, scale, scale);

    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

    gl.glVertexPointer(3, GL11.GL_FLOAT, 0, VERTICES);
    gl.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, COLORS);

    gl.glDrawElements(GL10.GL_TRIANGLE_FAN, 18,
      GL10.GL_UNSIGNED_BYTE, TRIANGLE_FAN_0);
    gl.glDrawElements(GL10.GL_TRIANGLE_FAN, 18,
      GL10.GL_UNSIGNED_BYTE, TRIANGLE_FAN_1);
  }

Finally, to satisfy the rest of the GLSurfaceView.Renderer interface, we provide empty implementations of onSurfaceChanged and onSurfaceCreated, as seen in the following code:

  @Override
  public void onSurfaceChanged(final GL10 gl, final int width,
    final int height) {
  }

  @Override
  public void onSurfaceCreated(final GL10 arg0,
    final EGLConfig config) {

}

Now, we are ready to integrate 3D tracking and rendering into our application.