From f1ede12a3953413120debe6e08d2ae9be4d1d55e Mon Sep 17 00:00:00 2001 From: Learn OpenGL ES Date: Wed, 9 May 2012 15:47:22 -0400 Subject: [PATCH] Added heightmap lesson. --- .../AndroidManifest.xml | 3 + .../res/drawable-hdpi/icon.png | Bin 11974 -> 0 bytes .../res/drawable-xhdpi/ic_lesson_eight.png | Bin 0 -> 7138 bytes .../res/drawable-xhdpi/icon.png | Bin 0 -> 7138 bytes .../raw/per_pixel_fragment_shader_no_tex.glsl | 32 ++ .../raw/per_pixel_vertex_shader_no_tex.glsl | 27 ++ .../res/values/strings.xml | 4 + .../android/TableOfContents.java | 10 + .../android/lesson7/LessonSevenActivity.java | 2 - .../android/lesson8/ErrorHandler.java | 10 + .../android/lesson8/LessonEightActivity.java | 64 +++ .../lesson8/LessonEightGLSurfaceView.java | 96 ++++ .../android/lesson8/LessonEightRenderer.java | 428 ++++++++++++++++++ 13 files changed, 674 insertions(+), 2 deletions(-) delete mode 100644 android/AndroidOpenGLESLessons/res/drawable-hdpi/icon.png create mode 100644 android/AndroidOpenGLESLessons/res/drawable-xhdpi/ic_lesson_eight.png create mode 100644 android/AndroidOpenGLESLessons/res/drawable-xhdpi/icon.png create mode 100644 android/AndroidOpenGLESLessons/res/raw/per_pixel_fragment_shader_no_tex.glsl create mode 100644 android/AndroidOpenGLESLessons/res/raw/per_pixel_vertex_shader_no_tex.glsl create mode 100644 android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/ErrorHandler.java create mode 100644 android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightActivity.java create mode 100644 android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightGLSurfaceView.java create mode 100644 android/AndroidOpenGLESLessons/src/com/learnopengles/android/lesson8/LessonEightRenderer.java 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 10bb8f4e4e0353e1f4a9bdb18015a668789c8104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11974 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 literal 0 HcmV?d00001 diff --git a/android/AndroidOpenGLESLessons/res/drawable-xhdpi/icon.png b/android/AndroidOpenGLESLessons/res/drawable-xhdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d924772482c7abab63d889cae8ccfd9561f03c GIT binary patch literal 7138 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 literal 0 HcmV?d00001 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; + } + } + } +}