2D Graphics Android

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.android.apis.graphics.kube;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
public class Kube extends Activity implements KubeRenderer.AnimationCallback {
  private GLWorld makeGLWorld() {
    GLWorld world = new GLWorld();
    int one = 0x10000;
    int half = 0x08000;
    GLColor red = new GLColor(one, 0, 0);
    GLColor green = new GLColor(0, one, 0);
    GLColor blue = new GLColor(0, 0, one);
    GLColor yellow = new GLColor(one, one, 0);
    GLColor orange = new GLColor(one, half, 0);
    GLColor white = new GLColor(one, one, one);
    GLColor black = new GLColor(0, 0, 0);
    // coordinates for our cubes
    float c0 = -1.0f;
    float c1 = -0.38f;
    float c2 = -0.32f;
    float c3 = 0.32f;
    float c4 = 0.38f;
    float c5 = 1.0f;
    // top back, left to right
    mCubes[0] = new Cube(world, c0, c4, c0, c1, c5, c1);
    mCubes[1] = new Cube(world, c2, c4, c0, c3, c5, c1);
    mCubes[2] = new Cube(world, c4, c4, c0, c5, c5, c1);
    // top middle, left to right
    mCubes[3] = new Cube(world, c0, c4, c2, c1, c5, c3);
    mCubes[4] = new Cube(world, c2, c4, c2, c3, c5, c3);
    mCubes[5] = new Cube(world, c4, c4, c2, c5, c5, c3);
    // top front, left to right
    mCubes[6] = new Cube(world, c0, c4, c4, c1, c5, c5);
    mCubes[7] = new Cube(world, c2, c4, c4, c3, c5, c5);
    mCubes[8] = new Cube(world, c4, c4, c4, c5, c5, c5);
    // middle back, left to right
    mCubes[9] = new Cube(world, c0, c2, c0, c1, c3, c1);
    mCubes[10] = new Cube(world, c2, c2, c0, c3, c3, c1);
    mCubes[11] = new Cube(world, c4, c2, c0, c5, c3, c1);
    // middle middle, left to right
    mCubes[12] = new Cube(world, c0, c2, c2, c1, c3, c3);
    mCubes[13] = null;
    mCubes[14] = new Cube(world, c4, c2, c2, c5, c3, c3);
    // middle front, left to right
    mCubes[15] = new Cube(world, c0, c2, c4, c1, c3, c5);
    mCubes[16] = new Cube(world, c2, c2, c4, c3, c3, c5);
    mCubes[17] = new Cube(world, c4, c2, c4, c5, c3, c5);
    // bottom back, left to right
    mCubes[18] = new Cube(world, c0, c0, c0, c1, c1, c1);
    mCubes[19] = new Cube(world, c2, c0, c0, c3, c1, c1);
    mCubes[20] = new Cube(world, c4, c0, c0, c5, c1, c1);
    // bottom middle, left to right
    mCubes[21] = new Cube(world, c0, c0, c2, c1, c1, c3);
    mCubes[22] = new Cube(world, c2, c0, c2, c3, c1, c3);
    mCubes[23] = new Cube(world, c4, c0, c2, c5, c1, c3);
    // bottom front, left to right
    mCubes[24] = new Cube(world, c0, c0, c4, c1, c1, c5);
    mCubes[25] = new Cube(world, c2, c0, c4, c3, c1, c5);
    mCubes[26] = new Cube(world, c4, c0, c4, c5, c1, c5);
    // paint the sides
    int i, j;
    // set all faces black by default
    for (i = 0; i < 27; i++) {
      Cube cube = mCubes[i];
      if (cube != null) {
        for (j = 0; j < 6; j++)
          cube.setFaceColor(j, black);
      }
    }
    // paint top
    for (i = 0; i < 9; i++)
      mCubes[i].setFaceColor(Cube.kTop, orange);
    // paint bottom
    for (i = 18; i < 27; i++)
      mCubes[i].setFaceColor(Cube.kBottom, red);
    // paint left
    for (i = 0; i < 27; i += 3)
      mCubes[i].setFaceColor(Cube.kLeft, yellow);
    // paint right
    for (i = 2; i < 27; i += 3)
      mCubes[i].setFaceColor(Cube.kRight, white);
    // paint back
    for (i = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        mCubes[i + j].setFaceColor(Cube.kBack, blue);
    // paint front
    for (i = 6; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        mCubes[i + j].setFaceColor(Cube.kFront, green);
    for (i = 0; i < 27; i++)
      if (mCubes[i] != null)
        world.addShape(mCubes[i]);
    // initialize our permutation to solved position
    mPermutation = new int[27];
    for (i = 0; i < mPermutation.length; i++)
      mPermutation[i] = i;
    createLayers();
    updateLayers();
    world.generate();
    return world;
  }
  private void createLayers() {
    mLayers[kUp] = new Layer(Layer.kAxisY);
    mLayers[kDown] = new Layer(Layer.kAxisY);
    mLayers[kLeft] = new Layer(Layer.kAxisX);
    mLayers[kRight] = new Layer(Layer.kAxisX);
    mLayers[kFront] = new Layer(Layer.kAxisZ);
    mLayers[kBack] = new Layer(Layer.kAxisZ);
    mLayers[kMiddle] = new Layer(Layer.kAxisX);
    mLayers[kEquator] = new Layer(Layer.kAxisY);
    mLayers[kSide] = new Layer(Layer.kAxisZ);
  }
  private void updateLayers() {
    Layer layer;
    GLShape[] shapes;
    int i, j, k;
    // up layer
    layer = mLayers[kUp];
    shapes = layer.mShapes;
    for (i = 0; i < 9; i++)
      shapes[i] = mCubes[mPermutation[i]];
    // down layer
    layer = mLayers[kDown];
    shapes = layer.mShapes;
    for (i = 18, k = 0; i < 27; i++)
      shapes[k++] = mCubes[mPermutation[i]];
    // left layer
    layer = mLayers[kLeft];
    shapes = layer.mShapes;
    for (i = 0, k = 0; i < 27; i += 9)
      for (j = 0; j < 9; j += 3)
        shapes[k++] = mCubes[mPermutation[i + j]];
    // right layer
    layer = mLayers[kRight];
    shapes = layer.mShapes;
    for (i = 2, k = 0; i < 27; i += 9)
      for (j = 0; j < 9; j += 3)
        shapes[k++] = mCubes[mPermutation[i + j]];
    // front layer
    layer = mLayers[kFront];
    shapes = layer.mShapes;
    for (i = 6, k = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        shapes[k++] = mCubes[mPermutation[i + j]];
    // back layer
    layer = mLayers[kBack];
    shapes = layer.mShapes;
    for (i = 0, k = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        shapes[k++] = mCubes[mPermutation[i + j]];
    // middle layer
    layer = mLayers[kMiddle];
    shapes = layer.mShapes;
    for (i = 1, k = 0; i < 27; i += 9)
      for (j = 0; j < 9; j += 3)
        shapes[k++] = mCubes[mPermutation[i + j]];
    // equator layer
    layer = mLayers[kEquator];
    shapes = layer.mShapes;
    for (i = 9, k = 0; i < 18; i++)
      shapes[k++] = mCubes[mPermutation[i]];
    // side layer
    layer = mLayers[kSide];
    shapes = layer.mShapes;
    for (i = 3, k = 0; i < 27; i += 9)
      for (j = 0; j < 3; j++)
        shapes[k++] = mCubes[mPermutation[i + j]];
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // We don't need a title either.
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    mView = new GLSurfaceView(getApplication());
    mRenderer = new KubeRenderer(makeGLWorld(), this);
    mView.setRenderer(mRenderer);
    setContentView(mView);
  }
  @Override
  protected void onResume() {
    super.onResume();
    mView.onResume();
  }
  @Override
  protected void onPause() {
    super.onPause();
    mView.onPause();
  }
  public void animate() {
    // change our angle of view
    mRenderer.setAngle(mRenderer.getAngle() + 1.2f);
    if (mCurrentLayer == null) {
      int layerID = mRandom.nextInt(9);
      mCurrentLayer = mLayers[layerID];
      mCurrentLayerPermutation = mLayerPermutations[layerID];
      mCurrentLayer.startAnimation();
      boolean direction = mRandom.nextBoolean();
      int count = mRandom.nextInt(3) + 1;
      count = 1;
      direction = false;
      mCurrentAngle = 0;
      if (direction) {
        mAngleIncrement = (float) Math.PI / 50;
        mEndAngle = mCurrentAngle + ((float) Math.PI * count) / 2f;
      } else {
        mAngleIncrement = -(float) Math.PI / 50;
        mEndAngle = mCurrentAngle - ((float) Math.PI * count) / 2f;
      }
    }
    mCurrentAngle += mAngleIncrement;
    if ((mAngleIncrement > 0f && mCurrentAngle >= mEndAngle)
        || (mAngleIncrement < 0f && mCurrentAngle <= mEndAngle)) {
      mCurrentLayer.setAngle(mEndAngle);
      mCurrentLayer.endAnimation();
      mCurrentLayer = null;
      // adjust mPermutation based on the completed layer rotation
      int[] newPermutation = new int[27];
      for (int i = 0; i < 27; i++) {
        newPermutation[i] = mPermutation[mCurrentLayerPermutation[i]];
        // newPermutation[i] =
        // mCurrentLayerPermutation[mPermutation[i]];
      }
      mPermutation = newPermutation;
      updateLayers();
    } else {
      mCurrentLayer.setAngle(mCurrentAngle);
    }
  }
  GLSurfaceView mView;
  KubeRenderer mRenderer;
  Cube[] mCubes = new Cube[27];
  // a Layer for each possible move
  Layer[] mLayers = new Layer[9];
  // permutations corresponding to a pi/2 rotation of each layer about its
  // axis
  static int[][] mLayerPermutations = {
      // permutation for UP layer
      { 2, 5, 8, 1, 4, 7, 0, 3, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
          19, 20, 21, 22, 23, 24, 25, 26 },
      // permutation for DOWN layer
      { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20,
          23, 26, 19, 22, 25, 18, 21, 24 },
      // permutation for LEFT layer
      { 6, 1, 2, 15, 4, 5, 24, 7, 8, 3, 10, 11, 12, 13, 14, 21, 16, 17,
          0, 19, 20, 9, 22, 23, 18, 25, 26 },
      // permutation for RIGHT layer
      { 0, 1, 8, 3, 4, 17, 6, 7, 26, 9, 10, 5, 12, 13, 14, 15, 16, 23,
          18, 19, 2, 21, 22, 11, 24, 25, 20 },
      // permutation for FRONT layer
      { 0, 1, 2, 3, 4, 5, 24, 15, 6, 9, 10, 11, 12, 13, 14, 25, 16, 7,
          18, 19, 20, 21, 22, 23, 26, 17, 8 },
      // permutation for BACK layer
      { 18, 9, 0, 3, 4, 5, 6, 7, 8, 19, 10, 1, 12, 13, 14, 15, 16, 17,
          20, 11, 2, 21, 22, 23, 24, 25, 26 },
      // permutation for MIDDLE layer
      { 0, 7, 2, 3, 16, 5, 6, 25, 8, 9, 4, 11, 12, 13, 14, 15, 22, 17,
          18, 1, 20, 21, 10, 23, 24, 19, 26 },
      // permutation for EQUATOR layer
      { 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 17, 10, 13, 16, 9, 12, 15, 18,
          19, 20, 21, 22, 23, 24, 25, 26 },
      // permutation for SIDE layer
      { 0, 1, 2, 21, 12, 3, 6, 7, 8, 9, 10, 11, 22, 13, 4, 15, 16, 17,
          18, 19, 20, 23, 14, 5, 24, 25, 26 } };
  // current permutation of starting position
  int[] mPermutation;
  // for random cube movements
  Random mRandom = new Random(System.currentTimeMillis());
  // currently turning layer
  Layer mCurrentLayer = null;
  // current and final angle for current Layer animation
  float mCurrentAngle, mEndAngle;
  // amount to increment angle
  float mAngleIncrement;
  int[] mCurrentLayerPermutation;
  // names for our 9 layers (based on notation from
  // http://www.cubefreak.net/notation.html)
  static final int kUp = 0;
  static final int kDown = 1;
  static final int kLeft = 2;
  static final int kRight = 3;
  static final int kFront = 4;
  static final int kBack = 5;
  static final int kMiddle = 6;
  static final int kEquator = 7;
  static final int kSide = 8;
}
class Cube extends GLShape {
  public Cube(GLWorld world, float left, float bottom, float back,
      float right, float top, float front) {
    super(world);
    GLVertex leftBottomBack = addVertex(left, bottom, back);
    GLVertex rightBottomBack = addVertex(right, bottom, back);
    GLVertex leftTopBack = addVertex(left, top, back);
    GLVertex rightTopBack = addVertex(right, top, back);
    GLVertex leftBottomFront = addVertex(left, bottom, front);
    GLVertex rightBottomFront = addVertex(right, bottom, front);
    GLVertex leftTopFront = addVertex(left, top, front);
    GLVertex rightTopFront = addVertex(right, top, front);
    // vertices are added in a clockwise orientation (when viewed from the
    // outside)
    // bottom
    addFace(new GLFace(leftBottomBack, leftBottomFront, rightBottomFront,
        rightBottomBack));
    // front
    addFace(new GLFace(leftBottomFront, leftTopFront, rightTopFront,
        rightBottomFront));
    // left
    addFace(new GLFace(leftBottomBack, leftTopBack, leftTopFront,
        leftBottomFront));
    // right
    addFace(new GLFace(rightBottomBack, rightBottomFront, rightTopFront,
        rightTopBack));
    // back
    addFace(new GLFace(leftBottomBack, rightBottomBack, rightTopBack,
        leftTopBack));
    // top
    addFace(new GLFace(leftTopBack, rightTopBack, rightTopFront,
        leftTopFront));
  }
  public static final int kBottom = 0;
  public static final int kFront = 1;
  public static final int kLeft = 2;
  public static final int kRight = 3;
  public static final int kBack = 4;
  public static final int kTop = 5;
}
class GLColor {
  public final int red;
  public final int green;
  public final int blue;
  public final int alpha;
  public GLColor(int red, int green, int blue, int alpha) {
    this.red = red;
    this.green = green;
    this.blue = blue;
    this.alpha = alpha;
  }
  public GLColor(int red, int green, int blue) {
    this.red = red;
    this.green = green;
    this.blue = blue;
    this.alpha = 0x10000;
  }
  @Override
  public boolean equals(Object other) {
    if (other instanceof GLColor) {
      GLColor color = (GLColor) other;
      return (red == color.red && green == color.green
          && blue == color.blue && alpha == color.alpha);
    }
    return false;
  }
}
class GLFace {
  public GLFace() {
  }
  // for triangles
  public GLFace(GLVertex v1, GLVertex v2, GLVertex v3) {
    addVertex(v1);
    addVertex(v2);
    addVertex(v3);
  }
  // for quadrilaterals
  public GLFace(GLVertex v1, GLVertex v2, GLVertex v3, GLVertex v4) {
    addVertex(v1);
    addVertex(v2);
    addVertex(v3);
    addVertex(v4);
  }
  public void addVertex(GLVertex v) {
    mVertexList.add(v);
  }
  // must be called after all vertices are added
  public void setColor(GLColor c) {
    int last = mVertexList.size() - 1;
    if (last < 2) {
      Log.e("GLFace", "not enough vertices in setColor()");
    } else {
      GLVertex vertex = mVertexList.get(last);
      // only need to do this if the color has never been set
      if (mColor == null) {
        while (vertex.color != null) {
          mVertexList.add(0, vertex);
          mVertexList.remove(last + 1);
          vertex = mVertexList.get(last);
        }
      }
      vertex.color = c;
    }
    mColor = c;
  }
  public int getIndexCount() {
    return (mVertexList.size() - 2) * 3;
  }
  public void putIndices(ShortBuffer buffer) {
    int last = mVertexList.size() - 1;
    GLVertex v0 = mVertexList.get(0);
    GLVertex vn = mVertexList.get(last);
    // push triangles into the buffer
    for (int i = 1; i < last; i++) {
      GLVertex v1 = mVertexList.get(i);
      buffer.put(v0.index);
      buffer.put(v1.index);
      buffer.put(vn.index);
      v0 = v1;
    }
  }
  private ArrayList mVertexList = new ArrayList();
  private GLColor mColor;
}
class GLShape {
  public GLShape(GLWorld world) {
    mWorld = world;
  }
  public void addFace(GLFace face) {
    mFaceList.add(face);
  }
  public void setFaceColor(int face, GLColor color) {
    mFaceList.get(face).setColor(color);
  }
  public void putIndices(ShortBuffer buffer) {
    Iterator iter = mFaceList.iterator();
    while (iter.hasNext()) {
      GLFace face = iter.next();
      face.putIndices(buffer);
    }
  }
  public int getIndexCount() {
    int count = 0;
    Iterator iter = mFaceList.iterator();
    while (iter.hasNext()) {
      GLFace face = iter.next();
      count += face.getIndexCount();
    }
    return count;
  }
  public GLVertex addVertex(float x, float y, float z) {
    // look for an existing GLVertex first
    Iterator iter = mVertexList.iterator();
    while (iter.hasNext()) {
      GLVertex vertex = iter.next();
      if (vertex.x == x && vertex.y == y && vertex.z == z) {
        return vertex;
      }
    }
    // doesn't exist, so create new vertex
    GLVertex vertex = mWorld.addVertex(x, y, z);
    mVertexList.add(vertex);
    return vertex;
  }
  public void animateTransform(M4 transform) {
    mAnimateTransform = transform;
    if (mTransform != null)
      transform = mTransform.multiply(transform);
    Iterator iter = mVertexList.iterator();
    while (iter.hasNext()) {
      GLVertex vertex = iter.next();
      mWorld.transformVertex(vertex, transform);
    }
  }
  public void startAnimation() {
  }
  public void endAnimation() {
    if (mTransform == null) {
      mTransform = new M4(mAnimateTransform);
    } else {
      mTransform = mTransform.multiply(mAnimateTransform);
    }
  }
  public M4 mTransform;
  public M4 mAnimateTransform;
  protected ArrayList mFaceList = new ArrayList();
  protected ArrayList mVertexList = new ArrayList();
  protected ArrayList mIndexList = new ArrayList(); // make
                                    // more
                                    // efficient?
  protected GLWorld mWorld;
}
class GLVertex {
  public float x;
  public float y;
  public float z;
  final short index; // index in vertex table
  GLColor color;
  GLVertex() {
    this.x = 0;
    this.y = 0;
    this.z = 0;
    this.index = -1;
  }
  GLVertex(float x, float y, float z, int index) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.index = (short) index;
  }
  @Override
  public boolean equals(Object other) {
    if (other instanceof GLVertex) {
      GLVertex v = (GLVertex) other;
      return (x == v.x && y == v.y && z == v.z);
    }
    return false;
  }
  static public int toFixed(float x) {
    return (int) (x * 65536.0f);
  }
  public void put(IntBuffer vertexBuffer, IntBuffer colorBuffer) {
    vertexBuffer.put(toFixed(x));
    vertexBuffer.put(toFixed(y));
    vertexBuffer.put(toFixed(z));
    if (color == null) {
      colorBuffer.put(0);
      colorBuffer.put(0);
      colorBuffer.put(0);
      colorBuffer.put(0);
    } else {
      colorBuffer.put(color.red);
      colorBuffer.put(color.green);
      colorBuffer.put(color.blue);
      colorBuffer.put(color.alpha);
    }
  }
  public void update(IntBuffer vertexBuffer, M4 transform) {
    // skip to location of vertex in mVertex buffer
    vertexBuffer.position(index * 3);
    if (transform == null) {
      vertexBuffer.put(toFixed(x));
      vertexBuffer.put(toFixed(y));
      vertexBuffer.put(toFixed(z));
    } else {
      GLVertex temp = new GLVertex();
      transform.multiply(this, temp);
      vertexBuffer.put(toFixed(temp.x));
      vertexBuffer.put(toFixed(temp.y));
      vertexBuffer.put(toFixed(temp.z));
    }
  }
}
class GLWorld {
  public void addShape(GLShape shape) {
    mShapeList.add(shape);
    mIndexCount += shape.getIndexCount();
  }
  public void generate() {
    ByteBuffer bb = ByteBuffer.allocateDirect(mVertexList.size() * 4 * 4);
    bb.order(ByteOrder.nativeOrder());
    mColorBuffer = bb.asIntBuffer();
    bb = ByteBuffer.allocateDirect(mVertexList.size() * 4 * 3);
    bb.order(ByteOrder.nativeOrder());
    mVertexBuffer = bb.asIntBuffer();
    bb = ByteBuffer.allocateDirect(mIndexCount * 2);
    bb.order(ByteOrder.nativeOrder());
    mIndexBuffer = bb.asShortBuffer();
    Iterator iter2 = mVertexList.iterator();
    while (iter2.hasNext()) {
      GLVertex vertex = iter2.next();
      vertex.put(mVertexBuffer, mColorBuffer);
    }
    Iterator iter3 = mShapeList.iterator();
    while (iter3.hasNext()) {
      GLShape shape = iter3.next();
      shape.putIndices(mIndexBuffer);
    }
  }
  public GLVertex addVertex(float x, float y, float z) {
    GLVertex vertex = new GLVertex(x, y, z, mVertexList.size());
    mVertexList.add(vertex);
    return vertex;
  }
  public void transformVertex(GLVertex vertex, M4 transform) {
    vertex.update(mVertexBuffer, transform);
  }
  int count = 0;
  public void draw(GL10 gl) {
    mColorBuffer.position(0);
    mVertexBuffer.position(0);
    mIndexBuffer.position(0);
    gl.glFrontFace(GL10.GL_CW);
    gl.glShadeModel(GL10.GL_FLAT);
    gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
    gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
    gl.glDrawElements(GL10.GL_TRIANGLES, mIndexCount,
        GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    count++;
  }
  static public float toFloat(int x) {
    return x / 65536.0f;
  }
  private ArrayList mShapeList = new ArrayList();
  private ArrayList mVertexList = new ArrayList();
  private int mIndexCount = 0;
  private IntBuffer mVertexBuffer;
  private IntBuffer mColorBuffer;
  private ShortBuffer mIndexBuffer;
}
/**
 * Example of how to use OpenGL|ES in a custom view
 * 
 */
class KubeRenderer implements GLSurfaceView.Renderer {
  public interface AnimationCallback {
    void animate();
  }
  public KubeRenderer(GLWorld world, AnimationCallback callback) {
    mWorld = world;
    mCallback = callback;
  }
  public void onDrawFrame(GL10 gl) {
    if (mCallback != null) {
      mCallback.animate();
    }
    /*
     * Usually, the first thing one might want to do is to clear the screen.
     * The most efficient way of doing this is to use glClear(). However we
     * must make sure to set the scissor correctly first. The scissor is
     * always specified in window coordinates:
     */
    gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    /*
     * Now we're ready to draw some 3D object
     */
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();
    gl.glTranslatef(0, 0, -3.0f);
    gl.glScalef(0.5f, 0.5f, 0.5f);
    gl.glRotatef(mAngle, 0, 1, 0);
    gl.glRotatef(mAngle * 0.25f, 1, 0, 0);
    gl.glColor4f(0.7f, 0.7f, 0.7f, 1.0f);
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    gl.glEnable(GL10.GL_CULL_FACE);
    gl.glShadeModel(GL10.GL_SMOOTH);
    gl.glEnable(GL10.GL_DEPTH_TEST);
    mWorld.draw(gl);
  }
  public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);
    /*
     * Set our projection matrix. This doesn't have to be done each time we
     * draw, but usually a new projection needs to be set when the viewport
     * is resized.
     */
    float ratio = (float) width / height;
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glFrustumf(-ratio, ratio, -1, 1, 2, 12);
    /*
     * By default, OpenGL enables features that improve quality but reduce
     * performance. One might want to tweak that especially on software
     * renderer.
     */
    gl.glDisable(GL10.GL_DITHER);
    gl.glActiveTexture(GL10.GL_TEXTURE0);
  }
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // Nothing special, don't have any textures we need to recreate.
  }
  public void setAngle(float angle) {
    mAngle = angle;
  }
  public float getAngle() {
    return mAngle;
  }
  private GLWorld mWorld;
  private AnimationCallback mCallback;
  private float mAngle;
}
class Layer {
  public Layer(int axis) {
    // start with identity matrix for transformation
    mAxis = axis;
    mTransform.setIdentity();
  }
  public void startAnimation() {
    for (int i = 0; i < mShapes.length; i++) {
      GLShape shape = mShapes[i];
      if (shape != null) {
        shape.startAnimation();
      }
    }
  }
  public void endAnimation() {
    for (int i = 0; i < mShapes.length; i++) {
      GLShape shape = mShapes[i];
      if (shape != null) {
        shape.endAnimation();
      }
    }
  }
  public void setAngle(float angle) {
    // normalize the angle
    float twopi = (float) Math.PI * 2f;
    while (angle >= twopi)
      angle -= twopi;
    while (angle < 0f)
      angle += twopi;
    // mAngle = angle;
    float sin = (float) Math.sin(angle);
    float cos = (float) Math.cos(angle);
    float[][] m = mTransform.m;
    switch (mAxis) {
    case kAxisX:
      m[1][1] = cos;
      m[1][2] = sin;
      m[2][1] = -sin;
      m[2][2] = cos;
      m[0][0] = 1f;
      m[0][1] = m[0][2] = m[1][0] = m[2][0] = 0f;
      break;
    case kAxisY:
      m[0][0] = cos;
      m[0][2] = sin;
      m[2][0] = -sin;
      m[2][2] = cos;
      m[1][1] = 1f;
      m[0][1] = m[1][0] = m[1][2] = m[2][1] = 0f;
      break;
    case kAxisZ:
      m[0][0] = cos;
      m[0][1] = sin;
      m[1][0] = -sin;
      m[1][1] = cos;
      m[2][2] = 1f;
      m[2][0] = m[2][1] = m[0][2] = m[1][2] = 0f;
      break;
    }
    for (int i = 0; i < mShapes.length; i++) {
      GLShape shape = mShapes[i];
      if (shape != null) {
        shape.animateTransform(mTransform);
      }
    }
  }
  GLShape[] mShapes = new GLShape[9];
  M4 mTransform = new M4();
  // float mAngle;
  // which axis do we rotate around?
  // 0 for X, 1 for Y, 2 for Z
  int mAxis;
  static public final int kAxisX = 0;
  static public final int kAxisY = 1;
  static public final int kAxisZ = 2;
}
class M4 {
  public float[][] m = new float[4][4];
  public M4() {
  }
  public M4(M4 other) {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        m[i][j] = other.m[i][j];
      }
    }
  }
  public void multiply(GLVertex src, GLVertex dest) {
    dest.x = src.x * m[0][0] + src.y * m[1][0] + src.z * m[2][0] + m[3][0];
    dest.y = src.x * m[0][1] + src.y * m[1][1] + src.z * m[2][1] + m[3][1];
    dest.z = src.x * m[0][2] + src.y * m[1][2] + src.z * m[2][2] + m[3][2];
  }
  public M4 multiply(M4 other) {
    M4 result = new M4();
    float[][] m1 = m;
    float[][] m2 = other.m;
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        result.m[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j]
            + m1[i][2] * m2[2][j] + m1[i][3] * m2[3][j];
      }
    }
    return result;
  }
  public void setIdentity() {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        m[i][j] = (i == j ? 1f : 0f);
      }
    }
  }
  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder("[ ");
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        builder.append(m[i][j]);
        builder.append(" ");
      }
      if (i < 2)
        builder.append("\n  ");
    }
    builder.append(" ]");
    return builder.toString();
  }
}