A number of beginer OpenGL examples show how to draw figures such as triangles and squares by specifying their explicit vertex coordinates using OpenGL Apis. When I really thought through this, a triangle and a square are a special case of a regular polygon.
A regular polygon according to wikipedia "is a polygon which is equiangular (all angles are equal in measure) and equilateral (all sides have the same length). Regular polygons may be convex or star".
So It is not a stretch, instead, to imagine specifying these regular polygons in terms of the number of sides, radius and an origin coordinate instead the "n" number of vertices.
The exercise here abstracts the OpenGL drawing of this RegularPolygon and in the process demonstrates how a) one can derive the vertices and drawing indecies based on the abstract definition of a figure b) Uses animation to start with a triangle and then end up with an approximated circle.
This example demonstrate the following aspects of Android OpenGL and also Java
Start by defining a regular polygon with the help of a radius, number of sides and its origin. Then calculate the vertices for each side of the polygon. Use GL_TRIANGLES to draw multiple triangles to make up the polygon. Then have the RegularPolygon provide these vertices and necessary indecies as an array to the drawing class.
Assuming that this RegularPolygon is already designed here is how I have used it in my code
int sides = 4;
RegularPolygon p = new RegularPolygon(0,0,0, // x,y,z of the origin
1, // Length of the radius
sides); // how many sides
//Allocate and fill the vertex buffer
this.mFVertexBuffer = p.getVertexBuffer();
//Allocate and fill the index buffer
this.mIndexBuffer = p.getIndexBuffer();
this.numOfIndecies = p.getNumberOfIndecies();
//set the buffers to their begining
this.mFVertexBuffer.position(0);
this.mIndexBuffer.position(0);
//set the color
gl.glColor4f(1.0f, 0, 0, 0.5f);
//set the vertex buffer
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
//draw
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndecies,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
The following files illustrate the design of this example
RegularPolygon.java: Implements the abstract idea of a regular polygon. Provides the ability to generate vertices and indecies to draw the figure.
AbstractRenderer.java: Derives from the Android OpenGL Renderer and acts as a base class for the PolygonRenderer that actually uses and draws the RegularPolygon object. This abstract class leaves the "draw" implementation undefined leaving to a derived class.
PolygonRenderer.java: Implementes the "draw" method where the actual drawing takes place.
OpenGLTestHarnessActivity: A simple activity class to instantiate the PolygonRenderer and facilitate drawing on a SurfaceView.
Source code for each of these files is presented below.
package com.ai.android.OpenGL.SDK15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import android.util.Log;
public class RegularPolygon
{
private float cx, cy, cz, r;
private int sides;
private float[] xarray = null;
private float[] yarray = null;
public RegularPolygon(float incx, float incy, float incz, // (x,y,z) center
float inr, // radius
int insides) // number of sides
{
cx = incx;
cy = incy;
cz = incz;
r = inr;
sides = insides;
xarray = new float[sides];
yarray = new float[sides];
calcArrays();
}
private void calcArrays()
{
float[] xmarray = this.getXMultiplierArray();
float[] ymarray = this.getYMultiplierArray();
//calc xarray
for(int i=0;i<sides;i++)
{
float curm = xmarray[i];
float xcoord = cx + r * curm;
xarray[i] = xcoord;
}
this.printArray(xarray, "xarray");
//calc yarray
for(int i=0;i<sides;i++)
{
float curm = ymarray[i];
float ycoord = cy + r * curm;
yarray[i] = ycoord;
}
this.printArray(yarray, "yarray");
}
public FloatBuffer getVertexBuffer()
{
int vertices = sides + 1;
int coordinates = 3;
int floatsize = 4;
int spacePerVertex = coordinates * floatsize;
ByteBuffer vbb = ByteBuffer.allocateDirect(spacePerVertex * vertices);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer mFVertexBuffer = vbb.asFloatBuffer();
//Put the first coordinate (x,y,z:0,0,0)
mFVertexBuffer.put(0.0f); //x
mFVertexBuffer.put(0.0f); //y
mFVertexBuffer.put(0.0f); //z
int totalPuts = 3;
for (int i=0;i<sides;i++)
{
mFVertexBuffer.put(xarray[i]); //x
mFVertexBuffer.put(yarray[i]); //y
mFVertexBuffer.put(0.0f); //z
totalPuts += 3;
}
Log.d("total puts:",Integer.toString(totalPuts));
return mFVertexBuffer;
}
public ShortBuffer getIndexBuffer()
{
short[] iarray = new short[sides * 3];
ByteBuffer ibb = ByteBuffer.allocateDirect(sides * 3 * 2);
ibb.order(ByteOrder.nativeOrder());
ShortBuffer mIndexBuffer = ibb.asShortBuffer();
for (int i=0;i<sides;i++)
{
short index1 = 0;
short index2 = (short)(i+1);
short index3 = (short)(i+2);
if (index3 == sides+1)
{
index3 = 1;
}
mIndexBuffer.put(index1);
mIndexBuffer.put(index2);
mIndexBuffer.put(index3);
iarray[i*3 + 0]=index1;
iarray[i*3 + 1]=index2;
iarray[i*3 + 2]=index3;
}
this.printShortArray(iarray, "index array");
return mIndexBuffer;
}
private float[] getXMultiplierArray()
{
float[] angleArray = getAngleArrays();
float[] xmultiplierArray = new float[sides];
for(int i=0;i<angleArray.length;i++)
{
float curAngle = angleArray[i];
float sinvalue = (float)Math.cos(Math.toRadians(curAngle));
float absSinValue = Math.abs(sinvalue);
if (isXPositiveQuadrant(curAngle))
{
sinvalue = absSinValue;
}
else
{
sinvalue = -absSinValue;
}
xmultiplierArray[i] = this.getApproxValue(sinvalue);
}
this.printArray(xmultiplierArray, "xmultiplierArray");
return xmultiplierArray;
}
private float[] getYMultiplierArray()
{
float[] angleArray = getAngleArrays();
float[] ymultiplierArray = new float[sides];
for(int i=0;i<angleArray.length;i++)
{
float curAngle = angleArray[i];
float sinvalue = (float)Math.sin(Math.toRadians(curAngle));
float absSinValue = Math.abs(sinvalue);
if (isYPositiveQuadrant(curAngle))
{
sinvalue = absSinValue;
}
else
{
sinvalue = -absSinValue;
}
ymultiplierArray[i] = this.getApproxValue(sinvalue);
}
this.printArray(ymultiplierArray, "ymultiplierArray");
return ymultiplierArray;
}
private boolean isXPositiveQuadrant(float angle)
{
if ((0 <= angle) && (angle <= 90))
{
return true;
}
if ((angle < 0) && (angle >= -90))
{
return true;
}
return false;
}
private boolean isYPositiveQuadrant(float angle)
{
if ((0 <= angle) && (angle <= 90))
{
return true;
}
if ((angle < 180) && (angle >= 90))
{
return true;
}
return false;
}
private float[] getAngleArrays()
{
float[] angleArray = new float[sides];
float commonAngle = 360.0f/sides;
float halfAngle = commonAngle/2.0f;
float firstAngle = 360.0f - (90+halfAngle);
angleArray[0] = firstAngle;
float curAngle = firstAngle;
for(int i=1;i<sides;i++)
{
float newAngle = curAngle - commonAngle;
angleArray[i] = newAngle;
curAngle = newAngle;
}
printArray(angleArray, "angleArray");
return angleArray;
}
private float getApproxValue(float f)
{
if (Math.abs(f) < 0.001)
{
return 0;
}
return f;
}
public int getNumberOfIndecies()
{
return sides * 3;
}
public static void test()
{
RegularPolygon triangle = new RegularPolygon(0,0,0,1,3);
}
private void printArray(float array[], String tag)
{
StringBuilder sb = new StringBuilder(tag);
for(int i=0;i<array.length;i++)
{
sb.append(";").append(array[i]);
}
Log.d("hh",sb.toString());
}
private void printShortArray(short array[], String tag)
{
StringBuilder sb = new StringBuilder(tag);
for(int i=0;i<array.length;i++)
{
sb.append(";").append(array[i]);
}
Log.d(tag,sb.toString());
}
}
package com.ai.android.OpenGL.SDK15;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLU;
import android.opengl.GLSurfaceView.Renderer;
//filename: AbstractRenderer.java
public abstract class AbstractRenderer implements Renderer
{
public int[] getConfigSpec() {
int[] configSpec = {
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_NONE
};
return configSpec;
}
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
GL10.GL_FASTEST);
gl.glClearColor(.5f, .5f, .5f, 1);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glEnable(GL10.GL_DEPTH_TEST);
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
float ratio = (float) w / h;
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
}
public void onDrawFrame(GL10 gl)
{
gl.glDisable(GL10.GL_DITHER);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
draw(gl);
}
protected abstract void draw(GL10 gl);
}
package com.ai.android.OpenGL.SDK15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.os.SystemClock;
//filename: SimpleTriangleRenderer.java
public class PolygonRenderer extends AbstractRenderer
{
//Number of points or vertices we want to use
private final static int VERTS = 4;
//A raw native buffer to hold the point coordinates
private FloatBuffer mFVertexBuffer;
//A raw native buffer to hold indices
//allowing a reuse of points.
private ShortBuffer mIndexBuffer;
private int numOfIndecies = 0;
private long prevtime = SystemClock.uptimeMillis();
private int sides = 3;
public PolygonRenderer(Context context)
{
//EvenPolygon t = new EvenPolygon(0,0,0,1,3);
//EvenPolygon t = new EvenPolygon(0,0,0,1,4);
prepareBuffers(sides);
}
private void prepareBuffers(int sides)
{
RegularPolygon t = new RegularPolygon(0,0,0,1,sides);
this.mFVertexBuffer = t.getVertexBuffer();
this.mIndexBuffer = t.getIndexBuffer();
this.numOfIndecies = t.getNumberOfIndecies();
this.mFVertexBuffer.position(0);
this.mIndexBuffer.position(0);
}
//overriden method
protected void draw(GL10 gl)
{
long curtime = SystemClock.uptimeMillis();
if ((curtime - prevtime) > 2000)
{
prevtime = curtime;
sides += 1;
if (sides > 20)
{
sides = 3;
}
this.prepareBuffers(sides);
}
//EvenPolygon.test();
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndecies,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}
package com.ai.android.OpenGL.SDK15;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
//filename: OpenGLTestHarnessActivity.java
public class OpenGL15TestHarnessActivity extends Activity {
private GLSurfaceView mTestHarness;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTestHarness = new GLSurfaceView(this);
mTestHarness.setEGLConfigChooser(false);
// mTestHarness.setRenderer(new SimpleRectRenderer(this));
// mTestHarness.setRenderer(new SimpleTriangleRenderer(this));
mTestHarness.setRenderer(new PolygonRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
setContentView(mTestHarness);
}
@Override
protected void onResume() {
super.onResume();
mTestHarness.onResume();
}
@Override
protected void onPause() {
super.onPause();
mTestHarness.onPause();
}
}
You will need some background on how OpenGL works in Android. If you are familiar with basics you can try to take these 4 files and build your application to run on the emulator. If not (I apologize as a plug is forthcoming) you can read chapter 10 and chapter 13 of the Pro Android from Apress to get the necessary background.
I will also try to post the zipped up project soon here and I will provide a link to the download.