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; + } + } + } +}