diff --git a/android/AndroidOpenGLESLessons/AndroidManifest.xml b/android/AndroidOpenGLESLessons/AndroidManifest.xml
index e48ba51..f0146aa 100644
--- a/android/AndroidOpenGLESLessons/AndroidManifest.xml
+++ b/android/AndroidOpenGLESLessons/AndroidManifest.xml
@@ -44,5 +44,8 @@
+
\ No newline at end of file
diff --git a/android/AndroidOpenGLESLessons/res/drawable-hdpi/icon.png b/android/AndroidOpenGLESLessons/res/drawable-hdpi/icon.png
deleted file mode 100644
index 10bb8f4..0000000
Binary files a/android/AndroidOpenGLESLessons/res/drawable-hdpi/icon.png and /dev/null differ
diff --git a/android/AndroidOpenGLESLessons/res/drawable-xhdpi/ic_lesson_eight.png b/android/AndroidOpenGLESLessons/res/drawable-xhdpi/ic_lesson_eight.png
new file mode 100644
index 0000000..f3d9247
Binary files /dev/null and b/android/AndroidOpenGLESLessons/res/drawable-xhdpi/ic_lesson_eight.png differ
diff --git a/android/AndroidOpenGLESLessons/res/drawable-xhdpi/icon.png b/android/AndroidOpenGLESLessons/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..f3d9247
Binary files /dev/null and b/android/AndroidOpenGLESLessons/res/drawable-xhdpi/icon.png differ
diff --git a/android/AndroidOpenGLESLessons/res/raw/per_pixel_fragment_shader_no_tex.glsl b/android/AndroidOpenGLESLessons/res/raw/per_pixel_fragment_shader_no_tex.glsl
new file mode 100644
index 0000000..4655f41
--- /dev/null
+++ b/android/AndroidOpenGLESLessons/res/raw/per_pixel_fragment_shader_no_tex.glsl
@@ -0,0 +1,32 @@
+precision mediump float; // Set the default precision to medium. We don't need as high of a
+ // precision in the fragment shader.
+uniform vec3 u_LightPos; // The position of the light in eye space.
+
+varying vec3 v_Position; // Interpolated position for this fragment.
+varying vec4 v_Color; // This is the color from the vertex shader interpolated across the
+ // triangle per fragment.
+varying vec3 v_Normal; // Interpolated normal for this fragment.
+
+// The entry point for our fragment shader.
+void main()
+{
+ // Will be used for attenuation.
+ float distance = length(u_LightPos - v_Position);
+
+ // Get a lighting direction vector from the light to the vertex.
+ vec3 lightVector = normalize(u_LightPos - v_Position);
+
+ // Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
+ // pointing in the same direction then it will get max illumination.
+ float diffuse = max(dot(v_Normal, lightVector), 0.0);
+
+ // Add attenuation.
+ diffuse = diffuse * (1.0 / (1.0 + (0.10 * distance)));
+
+ // Add ambient lighting
+ diffuse = diffuse + 0.3;
+
+ // Multiply the color by the diffuse illumination level to get final output color.
+ gl_FragColor = (v_Color * diffuse);
+}
+
diff --git a/android/AndroidOpenGLESLessons/res/raw/per_pixel_vertex_shader_no_tex.glsl b/android/AndroidOpenGLESLessons/res/raw/per_pixel_vertex_shader_no_tex.glsl
new file mode 100644
index 0000000..bb92053
--- /dev/null
+++ b/android/AndroidOpenGLESLessons/res/raw/per_pixel_vertex_shader_no_tex.glsl
@@ -0,0 +1,27 @@
+uniform mat4 u_MVPMatrix; // A constant representing the combined model/view/projection matrix.
+uniform mat4 u_MVMatrix; // A constant representing the combined model/view matrix.
+
+attribute vec4 a_Position; // Per-vertex position information we will pass in.
+attribute vec4 a_Color; // Per-vertex color information we will pass in.
+attribute vec3 a_Normal; // Per-vertex normal information we will pass in.
+
+varying vec3 v_Position; // This will be passed into the fragment shader.
+varying vec4 v_Color; // This will be passed into the fragment shader.
+varying vec3 v_Normal; // This will be passed into the fragment shader.
+
+// The entry point for our vertex shader.
+void main()
+{
+ // Transform the vertex into eye space.
+ v_Position = vec3(u_MVMatrix * a_Position);
+
+ // Pass through the color.
+ v_Color = a_Color;
+
+ // Transform the normal's orientation into eye space.
+ v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
+
+ // gl_Position is a special variable used to store the final position.
+ // Multiply the vertex by the matrix to get the final point in normalized screen coordinates.
+ gl_Position = u_MVPMatrix * a_Position;
+}
\ No newline at end of file
diff --git a/android/AndroidOpenGLESLessons/res/values/strings.xml b/android/AndroidOpenGLESLessons/res/values/strings.xml
index c14604c..c1f4a28 100644
--- a/android/AndroidOpenGLESLessons/res/values/strings.xml
+++ b/android/AndroidOpenGLESLessons/res/values/strings.xml
@@ -40,4 +40,8 @@
Not using VBOs
Using stride
Not using stride
+ Lesson Eight: An Intro to IBOs
+ This lesson looks at index buffer objects (IBOs).
+ Could not create vertex buffer object: %s
+ Unknown error: %s
\ No newline at end of file
diff --git a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/TableOfContents.java b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/TableOfContents.java
index 1cf0b8b..87b02b2 100644
--- a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/TableOfContents.java
+++ b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/TableOfContents.java
@@ -21,6 +21,7 @@
import com.learnopengles.android.lesson5.LessonFiveActivity;
import com.learnopengles.android.lesson6.LessonSixActivity;
import com.learnopengles.android.lesson7.LessonSevenActivity;
+import com.learnopengles.android.lesson8.LessonEightActivity;
public class TableOfContents extends ListActivity
{
@@ -104,6 +105,15 @@ public void onCreate(Bundle savedInstanceState)
activityMapping.put(i++, LessonSevenActivity.class);
}
+ {
+ final Map item = new HashMap();
+ item.put(ITEM_IMAGE, R.drawable.ic_lesson_eight);
+ item.put(ITEM_TITLE, getText(R.string.lesson_eight));
+ item.put(ITEM_SUBTITLE, getText(R.string.lesson_eight_subtitle));
+ data.add(item);
+ activityMapping.put(i++, LessonEightActivity.class);
+ }
+
final SimpleAdapter dataAdapter = new SimpleAdapter(this, data, R.layout.toc_item, new String[] {ITEM_IMAGE, ITEM_TITLE, ITEM_SUBTITLE}, new int[] {R.id.Image, R.id.Title, R.id.SubTitle});
setListAdapter(dataAdapter);
diff --git a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson7/LessonSevenActivity.java b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson7/LessonSevenActivity.java
index 54ea42b..74235c0 100644
--- a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson7/LessonSevenActivity.java
+++ b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson7/LessonSevenActivity.java
@@ -25,8 +25,6 @@ public void onCreate(Bundle savedInstanceState) {
mGLSurfaceView = (LessonSevenGLSurfaceView) findViewById(R.id.gl_surface_view);
- // We need the
-
// Check if the system supports OpenGL ES 2.0.
final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
diff --git a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/ErrorHandler.java b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/ErrorHandler.java
new file mode 100644
index 0000000..9a4c45b
--- /dev/null
+++ b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/ErrorHandler.java
@@ -0,0 +1,10 @@
+package com.learnopengles.android.lesson8;
+
+
+interface ErrorHandler {
+ enum ErrorType {
+ BUFFER_CREATION_ERROR
+ }
+
+ void handleError(ErrorType errorType, String cause);
+}
\ No newline at end of file
diff --git a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightActivity.java b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightActivity.java
new file mode 100644
index 0000000..2d006f4
--- /dev/null
+++ b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightActivity.java
@@ -0,0 +1,64 @@
+package com.learnopengles.android.lesson8;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.learnopengles.android.R;
+
+public class LessonEightActivity extends Activity {
+ private LessonEightGLSurfaceView glSurfaceView;
+ private LessonEightRenderer renderer;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ glSurfaceView = new LessonEightGLSurfaceView(this);
+
+ setContentView(glSurfaceView);
+
+ // Check if the system supports OpenGL ES 2.0.
+ final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
+ final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
+
+ if (supportsEs2) {
+ // Request an OpenGL ES 2.0 compatible context.
+ glSurfaceView.setEGLContextClientVersion(2);
+
+ final DisplayMetrics displayMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+
+ // Set the renderer to our demo renderer, defined below.
+ renderer = new LessonEightRenderer(this, glSurfaceView);
+ glSurfaceView.setRenderer(renderer, displayMetrics.density);
+ } else {
+ // This is where you could create an OpenGL ES 1.x compatible
+ // renderer if you wanted to support both ES 1 and ES 2.
+ return;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ // The activity must call the GL surface view's onResume() on activity
+ // onResume().
+ super.onResume();
+ glSurfaceView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // The activity must call the GL surface view's onPause() on activity
+ // onPause().
+ super.onPause();
+ glSurfaceView.onPause();
+ }
+}
\ No newline at end of file
diff --git a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightGLSurfaceView.java b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightGLSurfaceView.java
new file mode 100644
index 0000000..3dd2c7f
--- /dev/null
+++ b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightGLSurfaceView.java
@@ -0,0 +1,96 @@
+package com.learnopengles.android.lesson8;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.Toast;
+
+import com.learnopengles.android.R;
+import com.learnopengles.android.lesson8.ErrorHandler.ErrorType;
+
+public class LessonEightGLSurfaceView extends GLSurfaceView implements ErrorHandler
+{
+ private LessonEightRenderer renderer;
+
+ // Offsets for touch events
+ private float previousX;
+ private float previousY;
+
+ private float density;
+
+ public LessonEightGLSurfaceView(Context context)
+ {
+ super(context);
+ }
+
+ public LessonEightGLSurfaceView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ }
+
+ @Override
+ public void handleError(final ErrorType errorType, final String cause) {
+ // Queue on UI thread.
+ post(new Runnable() {
+ @Override
+ public void run() {
+ final String text;
+
+ switch (errorType) {
+ case BUFFER_CREATION_ERROR:
+ text = String
+ .format(getContext().getResources().getString(
+ R.string.lesson_eight_error_could_not_create_vbo), cause);
+ break;
+ default:
+ text = String.format(
+ getContext().getResources().getString(
+ R.string.lesson_eight_error_unknown), cause);
+ }
+
+ Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
+
+ }
+ });
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ if (event != null)
+ {
+ float x = event.getX();
+ float y = event.getY();
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE)
+ {
+ if (renderer != null)
+ {
+ float deltaX = (x - previousX) / density / 2f;
+ float deltaY = (y - previousY) / density / 2f;
+
+ renderer.deltaX += deltaX;
+ renderer.deltaY += deltaY;
+ }
+ }
+
+ previousX = x;
+ previousY = y;
+
+ return true;
+ }
+ else
+ {
+ return super.onTouchEvent(event);
+ }
+ }
+
+ // Hides superclass method.
+ public void setRenderer(LessonEightRenderer renderer, float density)
+ {
+ this.renderer = renderer;
+ this.density = density;
+ super.setRenderer(renderer);
+ }
+}
diff --git a/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightRenderer.java b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightRenderer.java
new file mode 100644
index 0000000..fd9a895
--- /dev/null
+++ b/android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightRenderer.java
@@ -0,0 +1,428 @@
+package com.learnopengles.android.lesson8;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.badlogic.gdx.backends.android.AndroidGL20;
+import com.learnopengles.android.R;
+import com.learnopengles.android.common.RawResourceReader;
+import com.learnopengles.android.common.ShaderHelper;
+import com.learnopengles.android.lesson8.ErrorHandler.ErrorType;
+
+/**
+ * This class implements our custom renderer. Note that the GL10 parameter
+ * passed in is unused for OpenGL ES 2.0 renderers -- the static class GLES20 is
+ * used instead.
+ */
+public class LessonEightRenderer implements GLSurfaceView.Renderer {
+ /** Used for debug logs. */
+ private static final String TAG = "LessonEightRenderer";
+
+ /** References to other main objects. */
+ private final LessonEightActivity lessonEightActivity;
+ private final ErrorHandler errorHandler;
+
+ /**
+ * Android's OpenGL bindings are broken until Gingerbread, so we use LibGDX
+ * bindings here.
+ */
+ private final AndroidGL20 glEs20;
+
+ /**
+ * Store the model matrix. This matrix is used to move models from object
+ * space (where each model can be thought of being located at the center of
+ * the universe) to world space.
+ */
+ private final float[] modelMatrix = new float[16];
+
+ /**
+ * Store the view matrix. This can be thought of as our camera. This matrix
+ * transforms world space to eye space; it positions things relative to our
+ * eye.
+ */
+ private final float[] viewMatrix = new float[16];
+
+ /**
+ * Store the projection matrix. This is used to project the scene onto a 2D
+ * viewport.
+ */
+ private final float[] projectionMatrix = new float[16];
+
+ /**
+ * Allocate storage for the final combined matrix. This will be passed into
+ * the shader program.
+ */
+ private final float[] mvpMatrix = new float[16];
+
+ /** Additional matrices. */
+ private final float[] accumulatedRotation = new float[16];
+ private final float[] currentRotation = new float[16];
+ private final float[] lightModelMatrix = new float[16];
+ private final float[] temporaryMatrix = new float[16];
+
+ /** OpenGL handles to our program uniforms. */
+ private int mvpMatrixUniform;
+ private int mvMatrixUniform;
+ private int lightPosUniform;
+
+ /** OpenGL handles to our program attributes. */
+ private int positionAttribute;
+ private int normalAttribute;
+ private int colorAttribute;
+
+ /** Identifiers for our uniforms and attributes inside the shaders. */
+ private static final String MVP_MATRIX_UNIFORM = "u_MVPMatrix";
+ private static final String MV_MATRIX_UNIFORM = "u_MVMatrix";
+ private static final String LIGHT_POSITION_UNIFORM = "u_LightPos";
+
+ private static final String POSITION_ATTRIBUTE = "a_Position";
+ private static final String NORMAL_ATTRIBUTE = "a_Normal";
+ private static final String COLOR_ATTRIBUTE = "a_Color";
+
+ /** Additional constants. */
+ private static final int POSITION_DATA_SIZE_IN_ELEMENTS = 3;
+ private static final int NORMAL_DATA_SIZE_IN_ELEMENTS = 3;
+ private static final int COLOR_DATA_SIZE_IN_ELEMENTS = 4;
+
+ private static final int BYTES_PER_FLOAT = 4;
+ private static final int BYTES_PER_SHORT = 2;
+
+ private static final int STRIDE = (POSITION_DATA_SIZE_IN_ELEMENTS + NORMAL_DATA_SIZE_IN_ELEMENTS + COLOR_DATA_SIZE_IN_ELEMENTS)
+ * BYTES_PER_FLOAT;
+
+ /**
+ * Used to hold a light centered on the origin in model space. We need a 4th
+ * coordinate so we can get translations to work when we multiply this by
+ * our transformation matrices.
+ */
+ private final float[] lightPosInModelSpace = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
+
+ /**
+ * Used to hold the current position of the light in world space (after
+ * transformation via model matrix).
+ */
+ private final float[] lightPosInWorldSpace = new float[4];
+
+ /**
+ * Used to hold the transformed position of the light in eye space (after
+ * transformation via modelview matrix)
+ */
+ private final float[] lightPosInEyeSpace = new float[4];
+
+ /** This is a handle to our cube shading program. */
+ private int program;
+
+ /** Retain the most recent delta for touch events. */
+ // These still work without volatile, but refreshes are not guaranteed to
+ // happen.
+ public volatile float deltaX;
+ public volatile float deltaY;
+
+ /** The current heightmap object. */
+ private HeightMap heightMap;
+
+ /**
+ * Initialize the model data.
+ */
+ public LessonEightRenderer(final LessonEightActivity lessonEightActivity, ErrorHandler errorHandler) {
+ this.lessonEightActivity = lessonEightActivity;
+ this.errorHandler = errorHandler;
+ glEs20 = new AndroidGL20();
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ heightMap = new HeightMap();
+
+ // Set the background clear color to black.
+ GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Enable depth testing
+ GLES20.glEnable(GLES20.GL_DEPTH_TEST);
+
+ // Position the eye in front of the origin.
+ final float eyeX = 0.0f;
+ final float eyeY = 0.0f;
+ final float eyeZ = -0.5f;
+
+ // We are looking toward the distance
+ final float lookX = 0.0f;
+ final float lookY = 0.0f;
+ final float lookZ = -5.0f;
+
+ // Set our up vector. This is where our head would be pointing were we
+ // holding the camera.
+ final float upX = 0.0f;
+ final float upY = 1.0f;
+ final float upZ = 0.0f;
+
+ // Set the view matrix. This matrix can be said to represent the camera
+ // position.
+ // NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination
+ // of a model and view matrix. In OpenGL 2, we can keep track of these
+ // matrices separately if we choose.
+ Matrix.setLookAtM(viewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
+
+ final String vertexShader = RawResourceReader.readTextFileFromRawResource(lessonEightActivity,
+ R.raw.per_pixel_vertex_shader_no_tex);
+ final String fragmentShader = RawResourceReader.readTextFileFromRawResource(lessonEightActivity,
+ R.raw.per_pixel_fragment_shader_no_tex);
+
+ final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
+ final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
+
+ program = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, new String[] {
+ POSITION_ATTRIBUTE, NORMAL_ATTRIBUTE, COLOR_ATTRIBUTE });
+
+ // Initialize the accumulated rotation matrix
+ Matrix.setIdentityM(accumulatedRotation, 0);
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+ // Set the OpenGL viewport to the same size as the surface.
+ GLES20.glViewport(0, 0, width, height);
+
+ // Create a new perspective projection matrix. The height will stay the
+ // same while the width will vary as per aspect ratio.
+ final float ratio = (float) width / height;
+ final float left = -ratio;
+ final float right = ratio;
+ final float bottom = -1.0f;
+ final float top = 1.0f;
+ final float near = 1.0f;
+ final float far = 1000.0f;
+
+ Matrix.frustumM(projectionMatrix, 0, left, right, bottom, top, near, far);
+ }
+
+ @Override
+ public void onDrawFrame(GL10 glUnused) {
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
+
+ // Set our per-vertex lighting program.
+ GLES20.glUseProgram(program);
+
+ // Set program handles for cube drawing.
+ mvpMatrixUniform = GLES20.glGetUniformLocation(program, MVP_MATRIX_UNIFORM);
+ mvMatrixUniform = GLES20.glGetUniformLocation(program, MV_MATRIX_UNIFORM);
+ lightPosUniform = GLES20.glGetUniformLocation(program, LIGHT_POSITION_UNIFORM);
+ positionAttribute = GLES20.glGetAttribLocation(program, POSITION_ATTRIBUTE);
+ normalAttribute = GLES20.glGetAttribLocation(program, NORMAL_ATTRIBUTE);
+ colorAttribute = GLES20.glGetAttribLocation(program, COLOR_ATTRIBUTE);
+
+ // Calculate position of the light. Push into the distance.
+ Matrix.setIdentityM(lightModelMatrix, 0);
+ Matrix.translateM(lightModelMatrix, 0, 0.0f, 7.5f, -8.0f);
+
+ Matrix.multiplyMV(lightPosInWorldSpace, 0, lightModelMatrix, 0, lightPosInModelSpace, 0);
+ Matrix.multiplyMV(lightPosInEyeSpace, 0, viewMatrix, 0, lightPosInWorldSpace, 0);
+
+ // Draw the heightmap.
+ // Translate the heightmap into the screen.
+ Matrix.setIdentityM(modelMatrix, 0);
+ Matrix.translateM(modelMatrix, 0, 0.0f, 0.0f, -12f);
+
+ // Set a matrix that contains the current rotation.
+ Matrix.setIdentityM(currentRotation, 0);
+ Matrix.rotateM(currentRotation, 0, deltaX, 0.0f, 1.0f, 0.0f);
+ Matrix.rotateM(currentRotation, 0, deltaY, 1.0f, 0.0f, 0.0f);
+ deltaX = 0.0f;
+ deltaY = 0.0f;
+
+ // Multiply the current rotation by the accumulated rotation, and then
+ // set the accumulated rotation to the result.
+ Matrix.multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
+ System.arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);
+
+ // Rotate the cube taking the overall rotation into account.
+ Matrix.multiplyMM(temporaryMatrix, 0, modelMatrix, 0, accumulatedRotation, 0);
+ System.arraycopy(temporaryMatrix, 0, modelMatrix, 0, 16);
+
+ // This multiplies the view matrix by the model matrix, and stores
+ // the result in the MVP matrix
+ // (which currently contains model * view).
+ Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0);
+
+ // Pass in the modelview matrix.
+ GLES20.glUniformMatrix4fv(mvMatrixUniform, 1, false, mvpMatrix, 0);
+
+ // This multiplies the modelview matrix by the projection matrix,
+ // and stores the result in the MVP matrix
+ // (which now contains model * view * projection).
+ Matrix.multiplyMM(temporaryMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);
+ System.arraycopy(temporaryMatrix, 0, mvpMatrix, 0, 16);
+
+ // Pass in the combined matrix.
+ GLES20.glUniformMatrix4fv(mvpMatrixUniform, 1, false, mvpMatrix, 0);
+
+ // Pass in the light position in eye space.
+ GLES20.glUniform3f(lightPosUniform, lightPosInEyeSpace[0], lightPosInEyeSpace[1], lightPosInEyeSpace[2]);
+
+ // Render the heightmap.
+ heightMap.render();
+ }
+
+ class HeightMap {
+ static final int SIZE_PER_SIDE = 32;
+ static final float MIN_POSITION = -5f;
+ static final float POSITION_RANGE = 10f;
+
+ final int[] vbo = new int[1];
+ final int[] ibo = new int[1];
+
+ int indexCount;
+
+ HeightMap() {
+ try {
+ final int floatsPerVertex = POSITION_DATA_SIZE_IN_ELEMENTS + NORMAL_DATA_SIZE_IN_ELEMENTS
+ + COLOR_DATA_SIZE_IN_ELEMENTS;
+ final int xLength = SIZE_PER_SIDE;
+ final int yLength = SIZE_PER_SIDE;
+
+ final float[] heightMapVertexData = new float[xLength * yLength * floatsPerVertex];
+
+ int offset = 0;
+
+ // First, build the data for the vertex buffer
+ for (int y = 0; y < yLength; y++) {
+ for (int x = 0; x < xLength; x++) {
+ final float xRatio = x / (float) (xLength - 1);
+ final float yRatio = y / (float) (yLength - 1);
+ final float xPosition = MIN_POSITION + (xRatio * POSITION_RANGE);
+ final float yPosition = MIN_POSITION + (yRatio * POSITION_RANGE);
+
+ // Position
+ heightMapVertexData[offset++] = xPosition;
+ heightMapVertexData[offset++] = yPosition;
+ heightMapVertexData[offset++] = ((xPosition * xPosition) + (yPosition * yPosition)) / 10f;
+
+ // Cheap normal using a derivative of the function.
+ // The slope for X will be 2X, for Y will be 2Y.
+ final float xNormal = (-2 * xPosition) / 10f;
+ final float yNormal = (-2 * yPosition) / 10f;
+ final float length = Matrix.length(xNormal, yNormal, 1f);
+
+ heightMapVertexData[offset++] = xNormal / length;
+ heightMapVertexData[offset++] = yNormal / length;
+ heightMapVertexData[offset++] = 1f / length;
+
+ // Add some fancy colors.
+ heightMapVertexData[offset++] = xRatio;
+ heightMapVertexData[offset++] = yRatio;
+ heightMapVertexData[offset++] = 0.5f;
+ heightMapVertexData[offset++] = 1f;
+ }
+ }
+
+ // Now build the index data
+ final int numStripsRequired = yLength - 1;
+ final int numDegensRequired = 2 * (numStripsRequired - 1);
+ final int verticesPerStrip = 2 * xLength;
+
+ final short[] heightMapIndexData = new short[(verticesPerStrip * numStripsRequired) + numDegensRequired];
+
+ offset = 0;
+
+ for (int y = 0; y < yLength - 1; y++) {
+ if (y > 0) {
+ // Degenerate begin: repeat first vertex
+ heightMapIndexData[offset++] = (short) (y * yLength);
+ }
+
+ for (int x = 0; x < xLength; x++) {
+ // One part of the strip
+ heightMapIndexData[offset++] = (short) ((y * yLength) + x);
+ heightMapIndexData[offset++] = (short) (((y + 1) * yLength) + x);
+ }
+
+ if (y < yLength - 2) {
+ // Degenerate end: repeat last vertex
+ heightMapIndexData[offset++] = (short) (((y + 1) * yLength) + (xLength - 1));
+ }
+ }
+
+ indexCount = heightMapIndexData.length;
+
+ final FloatBuffer heightMapVertexDataBuffer = ByteBuffer
+ .allocateDirect(heightMapVertexData.length * BYTES_PER_FLOAT).order(ByteOrder.nativeOrder())
+ .asFloatBuffer();
+ heightMapVertexDataBuffer.put(heightMapVertexData).position(0);
+
+ final ShortBuffer heightMapIndexDataBuffer = ByteBuffer
+ .allocateDirect(heightMapIndexData.length * BYTES_PER_SHORT).order(ByteOrder.nativeOrder())
+ .asShortBuffer();
+ heightMapIndexDataBuffer.put(heightMapIndexData).position(0);
+
+ GLES20.glGenBuffers(1, vbo, 0);
+ GLES20.glGenBuffers(1, ibo, 0);
+
+ if (vbo[0] > 0 && ibo[0] > 0) {
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0]);
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, heightMapVertexDataBuffer.capacity() * BYTES_PER_FLOAT,
+ heightMapVertexDataBuffer, GLES20.GL_STATIC_DRAW);
+
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ibo[0]);
+ GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, heightMapIndexDataBuffer.capacity()
+ * BYTES_PER_SHORT, heightMapIndexDataBuffer, GLES20.GL_STATIC_DRAW);
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+ } else {
+ errorHandler.handleError(ErrorType.BUFFER_CREATION_ERROR, "glGenBuffers");
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, t);
+ errorHandler.handleError(ErrorType.BUFFER_CREATION_ERROR, t.getLocalizedMessage());
+ }
+ }
+
+ void render() {
+ if (vbo[0] > 0 && ibo[0] > 0) {
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0]);
+
+ // Bind Attributes
+ glEs20.glVertexAttribPointer(positionAttribute, POSITION_DATA_SIZE_IN_ELEMENTS, GLES20.GL_FLOAT, false,
+ STRIDE, 0);
+ GLES20.glEnableVertexAttribArray(positionAttribute);
+
+ glEs20.glVertexAttribPointer(normalAttribute, NORMAL_DATA_SIZE_IN_ELEMENTS, GLES20.GL_FLOAT, false,
+ STRIDE, POSITION_DATA_SIZE_IN_ELEMENTS * BYTES_PER_FLOAT);
+ GLES20.glEnableVertexAttribArray(normalAttribute);
+
+ glEs20.glVertexAttribPointer(colorAttribute, COLOR_DATA_SIZE_IN_ELEMENTS, GLES20.GL_FLOAT, false,
+ STRIDE, (POSITION_DATA_SIZE_IN_ELEMENTS + NORMAL_DATA_SIZE_IN_ELEMENTS) * BYTES_PER_FLOAT);
+ GLES20.glEnableVertexAttribArray(colorAttribute);
+
+ // Draw
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ibo[0]);
+ glEs20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_SHORT, 0);
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+ }
+
+ void release() {
+ if (vbo[0] > 0) {
+ GLES20.glDeleteBuffers(vbo.length, vbo, 0);
+ vbo[0] = 0;
+ }
+
+ if (ibo[0] > 0) {
+ GLES20.glDeleteBuffers(ibo.length, ibo, 0);
+ ibo[0] = 0;
+ }
+ }
+ }
+}