/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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.google.fpl.gamecontroller;
import android.content.res.Resources;
import android.opengl.GLES20;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
* Handles collecting and rendering of triangles.
*/
public class ShapeBuffer {
// Number of coordinates per vertex in this array.
private static final int COORDS_PER_VERTEX = 3;
// 4 bytes per vertex.
private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * 4;
// Number of color components in each vertex.
private static final int COLOR_CHANNELS_PER_VERTEX = 4;
// Each color component is one byte.
private static final int COLOR_STRIDE = COLOR_CHANNELS_PER_VERTEX;
private static final int MAX_BUFFER_SIZE = 50000;
// Names used in the vertex and fragment shaders.
private static final String POSITION_ATTRIBUTE_NAME = "position";
private static final String COLOR_ATTRIBUTE_NAME = "color";
private static final String MVP_MATRIX_UNIFORM_NAME = "mvpMatrix";
private int mShaderProgram = 0;
private int mCurrentIndex;
private final float[] mVertexData;
private final int[] mColorData;
private final IntBuffer mColorBuffer;
private final FloatBuffer mVertexBuffer;
private int mPositionAttributeHandle;
private int mColorAttributeHandle;
private int mMVPMatrixUniformHandle;
public ShapeBuffer() {
mVertexData = new float[COORDS_PER_VERTEX * MAX_BUFFER_SIZE];
// All 4 color channels are packed into a single int, so only need one int per vertex.
mColorData = new int[MAX_BUFFER_SIZE];
clear();
ByteBuffer bb;
// size = buffer size x4 values per color, x1 bytes per color.
bb = ByteBuffer.allocateDirect(MAX_BUFFER_SIZE * COLOR_STRIDE);
bb.order(ByteOrder.nativeOrder());
mColorBuffer = bb.asIntBuffer();
// size = buffer size x3 values per coord, x4 bytes per float
bb = ByteBuffer.allocateDirect(MAX_BUFFER_SIZE * VERTEX_STRIDE);
bb.order(ByteOrder.nativeOrder());
mVertexBuffer = bb.asFloatBuffer();
}
/**
*/
/**
* Loads the shaders and looks up uniform and attribute locations.
*
* @return true if the shaders are successfully loaded and compiled.
*/
public boolean loadResources() {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
getRawAsset(R.raw.untextured_vs));
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
getRawAsset(R.raw.untextured_fs));
// Create empty OpenGL Program.
int program = GLES20.glCreateProgram();
if (program != 0) {
// Add the vertex shader to program.
GLES20.glAttachShader(program, vertexShader);
// Add the fragment shader to program.
GLES20.glAttachShader(program, fragmentShader);
// Create OpenGL program executables.
GLES20.glLinkProgram(program);
checkGlError("glLinkProgram");
// Get handle to vertex shader's position attribute.
mPositionAttributeHandle = GLES20.glGetAttribLocation(program, POSITION_ATTRIBUTE_NAME);
checkGlError("glGetAttribLocation - position");
// Get handle to vertex shader's color attribute.
mColorAttributeHandle = GLES20.glGetAttribLocation(program, COLOR_ATTRIBUTE_NAME);
checkGlError("glGetAttribLocation - color");
// Get handle to shape's transformation matrix.
mMVPMatrixUniformHandle = GLES20.glGetUniformLocation(program, MVP_MATRIX_UNIFORM_NAME);
checkGlError("glGetUniformLocation");
mShaderProgram = program;
}
return mShaderProgram != 0;
}
/**
* Returns false if initialization has failed, for example if one of the shaders
* failed to load.
*/
public boolean isInitialized() {
return mShaderProgram != 0;
}
/**
* Reset the buffer to start collecting new data.
*/
public void clear() {
mCurrentIndex = 0;
}
public void add2DShape(float centerX, float centerY, Utils.Color color,
float[] xyPositionOffsets, float scaleX,
float scaleY, float headingX, float headingY) {
// Normalize the heading vector.
float magnitude = Utils.vector2DLength(headingX, headingY);
if (magnitude == 0.0f) {
headingX = 0.0f;
headingY = 1.0f;
} else {
headingX /= magnitude;
headingY /= magnitude;
}
final int packedABGRColor = color.getPackedABGR();
for (int i = 0; i < xyPositionOffsets.length - 1; i += 2) {
final float positionX = xyPositionOffsets[i + 0];
final float positionY = xyPositionOffsets[i + 1];
float cx = (scaleX * positionX * headingX - scaleY * positionY * headingY);
float cy = (scaleX * positionX * headingY + scaleY * positionY * headingX);
// Compute the x and y positions.
final int vertexOffset = mCurrentIndex * COORDS_PER_VERTEX;
mVertexData[vertexOffset + 0] = cx + centerX;
mVertexData[vertexOffset + 1] = cy + centerY;
// Always set z to 0.
mVertexData[vertexOffset + 2] = 0.0f;
mColorData[mCurrentIndex] = packedABGRColor;
++mCurrentIndex;
// If we're on the first or last point, repeat it for stiching.
if (i == 0) {
stitchingHelper();
}
}
stitchingHelper();
}
/**
* Draws all the triangles currently in the buffer.
*
* @param mvpMatrix the combined model, view, and projection matrices.
*/
public void draw(float[] mvpMatrix) {
if (mCurrentIndex == 0) {
// Nothing to draw.
return;
}
// Load up our data:
checkGlError("draw init");
mVertexBuffer.clear();
mVertexBuffer.put(mVertexData);
mVertexBuffer.position(0);
mColorBuffer.clear();
mColorBuffer.put(mColorData);
mColorBuffer.position(0);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
checkGlError("draw start");
// Add program to OpenGL environment.
GLES20.glUseProgram(mShaderProgram);
checkGlError("glUseProgram");
// Enable a handle to the triangle vertices.
GLES20.glEnableVertexAttribArray(mPositionAttributeHandle);
// Prepare the triangle coordinate data.
GLES20.glVertexAttribPointer(mPositionAttributeHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, VERTEX_STRIDE, mVertexBuffer);
checkGlError("glVertexAttribPointer - vert");
// Enable a handle to the triangle vertices.
GLES20.glEnableVertexAttribArray(mColorAttributeHandle);
// Prepare the color data.
GLES20.glVertexAttribPointer(mColorAttributeHandle, COLOR_CHANNELS_PER_VERTEX,
GLES20.GL_UNSIGNED_BYTE, true, COLOR_STRIDE, mColorBuffer);
checkGlError("glVertexAttribPointer - color");
// Apply the projection and view transformation.
GLES20.glUniformMatrix4fv(mMVPMatrixUniformHandle, 1, false, mvpMatrix, 0);
checkGlError("glUniformMatrix4fv");
// Draw the buffers!
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mCurrentIndex);
checkGlError("draw call");
// Disable vertex arrays.
GLES20.glDisableVertexAttribArray(mPositionAttributeHandle);
checkGlError("position attrib arrays disabled");
GLES20.glDisableVertexAttribArray(mColorAttributeHandle);
checkGlError("vertex attrib arrays disabled");
}
/**
* Utility method for debugging OpenGL calls. Provide the name of the call
* just after making it.
*
* If the operation is not successful, the check throws an error.
*
* @param glOperation Name of the OpenGL call to check.
*/
private static void checkGlError(String glOperation) {
if (BuildConfig.DEBUG) {
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
Utils.logError(glOperation + ": glError " + error);
throw new RuntimeException(glOperation + ": glError " + error);
}
}
}
/**
* Loads the given raw resource into a string.
* @param id the id of the resource to load.
* @return the given resource as a string.
*/
private static String getRawAsset(int id) {
Resources res = GameState.getInstance().getResources();
return fromStream(res.openRawResource(id));
}
/**
* Parses the given input stream into a string.
*
* @param in an InputStream.
* @return a string.
*/
private static String fromStream(InputStream in) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String newLine = System.getProperty("line.separator");
String line;
try {
while ((line = reader.readLine()) != null) {
out.append(line);
out.append(newLine);
}
} catch (IOException e) {
Utils.logError(e.toString());
}
return out.toString();
}
/**
* Creates an OpenGl shader object for the given shader source code.
*/
private static int loadShader(int type, String shaderCode) {
// Create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER).
int shader = GLES20.glCreateShader(type);
// Add the source code to the shader and compile it.
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
/**
* Duplicates the last point in our vertex list, to aid in stitching triangle strips.
*/
private void stitchingHelper() {
// Copy the x, y, and x vertex components.
mVertexData[3 * mCurrentIndex + 0] = mVertexData[3 * mCurrentIndex + 0 - 3];
mVertexData[3 * mCurrentIndex + 1] = mVertexData[3 * mCurrentIndex + 1 - 3];
mVertexData[3 * mCurrentIndex + 2] = mVertexData[3 * mCurrentIndex + 2 - 3];
// Copy the color component.
mColorData[mCurrentIndex] = mColorData[mCurrentIndex - 1];
++mCurrentIndex;
}
}