package org.lwjgl.test.opengles.util; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; import static org.lwjgl.opengles.GLES20.*; /** VBO implementation of GLU Sphere. */ public final class Sphere { /* QuadricNormal */ public static final int GLU_SMOOTH = 100000; public static final int GLU_FLAT = 100001; public static final int GLU_NONE = 100002; /* QuadricDrawStyle */ public static final int GLU_POINT = 100010; public static final int GLU_LINE = 100011; public static final int GLU_FILL = 100012; public static final int GLU_SILHOUETTE = 100013; /* QuadricOrientation */ public static final int GLU_OUTSIDE = 100020; public static final int GLU_INSIDE = 100021; static final float PI = (float)Math.PI; private int drawStyle; private int orientation; private boolean textureFlag; private int normals; private BufferObjectArray bo; private final List<DrawCommand> drawCommands = new ArrayList<DrawCommand>(4); public Sphere() { this(GLU_FILL, GLU_OUTSIDE, false, GLU_SMOOTH); } public Sphere(final int drawStyle, final int orientation, final boolean textureFlag, final int normals) { setDrawStyle(drawStyle); setOrientation(orientation); setTextureFlag(textureFlag); setNormals(normals); } public Sphere(final float radius, final int slices, final int stacks) { this(); updateGeometry(radius, slices, stacks); } public Sphere(final float radius, final int slices, final int stacks, final int drawStyle, final int orientation, final boolean textureFlag, final int normals) { this(drawStyle, orientation, textureFlag, normals); updateGeometry(radius, slices, stacks); } public void updateGeometry(final float radius, final int slices, final int stacks) { if ( bo != null ) destroy(); bo = new BufferObjectArray(GL_STATIC_DRAW, createBuffer(radius, slices, stacks)); } public void bind() { bo.enable(); } public void draw() { for ( DrawCommand command : drawCommands ) command.draw(); } public void destroy() { bo.destroy(); bo = null; drawCommands.clear(); } /** * specifies the draw style for quadrics. * <p/> * The legal values are as follows: * <p/> * GLU.FILL: Quadrics are rendered with polygon primitives. The polygons * are drawn in a counterclockwise fashion with respect to * their normals (as defined with glu.quadricOrientation). * <p/> * GLU.LINE: Quadrics are rendered as a set of lines. * <p/> * GLU.SILHOUETTE: Quadrics are rendered as a set of lines, except that edges * separating coplanar faces will not be drawn. * <p/> * GLU.POINT: Quadrics are rendered as a set of points. * * @param drawStyle The drawStyle to set */ public void setDrawStyle(int drawStyle) { switch ( drawStyle ) { case GLU_FILL: case GLU_LINE: case GLU_SILHOUETTE: case GLU_POINT: break; default: throw new IllegalArgumentException("Invalid draw style specified: " + drawStyle); } this.drawStyle = drawStyle; } /** * specifies what kind of normals are desired for quadrics. * The legal values are as follows: * <p/> * GLU.NONE: No normals are generated. * <p/> * GLU.FLAT: One normal is generated for every facet of a quadric. * <p/> * GLU.SMOOTH: One normal is generated for every vertex of a quadric. This * is the default. * * @param normals The normals to set */ public void setNormals(int normals) { switch ( normals ) { case GLU_NONE: case GLU_FLAT: case GLU_SMOOTH: break; default: throw new IllegalArgumentException("Invalid normal kind specified: " + normals); } this.normals = normals; } /** * specifies what kind of orientation is desired for. * The orientation values are as follows: * <p/> * GLU.OUTSIDE: Quadrics are drawn with normals pointing outward. * <p/> * GLU.INSIDE: Normals point inward. The default is GLU.OUTSIDE. * <p/> * Note that the interpretation of outward and inward depends on the quadric * being drawn. * * @param orientation The orientation to set */ public void setOrientation(int orientation) { if ( orientation != GLU_OUTSIDE && orientation != GLU_INSIDE ) throw new IllegalArgumentException("Invalid orientation specified: " + orientation); this.orientation = orientation; } /** * specifies if texture coordinates should be generated for * quadrics rendered with qobj. If the value of textureCoords is true, * then texture coordinates are generated, and if textureCoords is false, * they are not.. The default is false. * <p/> * The manner in which texture coordinates are generated depends upon the * specific quadric rendered. * * @param textureFlag The textureFlag to set */ public void setTextureFlag(boolean textureFlag) { this.textureFlag = textureFlag; } /** * Returns the drawStyle. * * @return int */ public int getDrawStyle() { return drawStyle; } /** * Returns the normals. * * @return int */ public int getNormals() { return normals; } /** * Returns the orientation. * * @return int */ public int getOrientation() { return orientation; } /** * Returns the textureFlag. * * @return boolean */ public boolean getTextureFlag() { return textureFlag; } private static float sin(final float r) { return (float)Math.sin(r); } private static float cos(final float r) { return (float)Math.cos(r); } private int addDrawCommand(final int mode, final int first, final int count) { drawCommands.add(new DrawCommand(mode, first, count)); return count; } /** * draws a sphere of the given radius centered around the origin. * The sphere is subdivided around the z axis into slices and along the z axis * into stacks (similar to lines of longitude and latitude). * <p/> * If the orientation is set to GLU.OUTSIDE (with glu.quadricOrientation), then * any normals generated point away from the center of the sphere. Otherwise, * they point toward the center of the sphere. * <p/> * If texturing is turned on (with glu.quadricTexture), then texture * coordinates are generated so that t ranges from 0.0 at z=-radius to 1.0 at * z=radius (t increases linearly along longitudinal lines), and s ranges from * 0.0 at the +y axis, to 0.25 at the +x axis, to 0.5 at the -y axis, to 0.75 * at the -x axis, and back to 1.0 at the +y axis. */ public FloatBuffer createBuffer(float radius, int slices, int stacks) { float rho, theta; float x, y, z; float s, t, ds, dt; int i, j; final boolean normals = this.normals != GLU_NONE; final float nsign = this.orientation == GLU_INSIDE ? -1.0f : 1.0f; final float drho = PI / stacks; final float dtheta = 2.0f * PI / slices; final ImmediateModeBuffer imb = new ImmediateModeBuffer(16 * 1024); // TODO: We can calculate this to avoid re-allocs int lastDrawIndex = 0; if ( this.drawStyle == GLU_FILL ) { if ( !this.textureFlag ) { lastDrawIndex += addDrawCommand(GL_TRIANGLE_FAN, lastDrawIndex, slices + 2); // draw +Z end as a triangle fan imb.glNormal3f(0.0f, 0.0f, 1.0f); imb.glVertex3f(0.0f, 0.0f, nsign * radius); for ( j = 0; j <= slices; j++ ) { theta = (j == slices) ? 0.0f : j * dtheta; x = -sin(theta) * sin(drho); y = cos(theta) * sin(drho); z = nsign * cos(drho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); imb.glVertex3f(x * radius, y * radius, z * radius); } } ds = 1.0f / slices; dt = 1.0f / stacks; t = 1.0f; // because loop now runs from 0 final int imin, imax; if ( this.textureFlag ) { imin = 0; imax = stacks; } else { imin = 1; imax = stacks - 1; } // draw intermediate stacks as quad strips for ( i = imin; i < imax; i++ ) { lastDrawIndex += addDrawCommand(GL_TRIANGLE_STRIP, lastDrawIndex, (slices + 1) * 2); rho = i * drho; s = 0.0f; for ( j = 0; j <= slices; j++ ) { theta = (j == slices) ? 0.0f : j * dtheta; x = -sin(theta) * sin(rho); y = cos(theta) * sin(rho); z = nsign * cos(rho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); if ( textureFlag ) imb.glTexCoord2f(s, t); imb.glVertex3f(x * radius, y * radius, z * radius); x = -sin(theta) * sin(rho + drho); y = cos(theta) * sin(rho + drho); z = nsign * cos(rho + drho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); if ( textureFlag ) imb.glTexCoord2f(s, t - dt); s += ds; imb.glVertex3f(x * radius, y * radius, z * radius); } t -= dt; } if ( !this.textureFlag ) { lastDrawIndex += addDrawCommand(GL_TRIANGLE_FAN, lastDrawIndex, slices + 2); // draw -Z end as a triangle fan imb.glNormal3f(0.0f, 0.0f, -1.0f); imb.glVertex3f(0.0f, 0.0f, -radius * nsign); rho = PI - drho; s = 1.0f; for ( j = slices; j >= 0; j-- ) { theta = (j == slices) ? 0.0f : j * dtheta; x = -sin(theta) * sin(rho); y = cos(theta) * sin(rho); z = nsign * cos(rho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); s -= ds; imb.glVertex3f(x * radius, y * radius, z * radius); } } } else if ( this.drawStyle == GLU_LINE || this.drawStyle == GLU_SILHOUETTE ) { // draw stack lines for ( i = 1; i < stacks; i++ ) { // stack line at i==stacks-1 was missing here lastDrawIndex += addDrawCommand(GL_LINE_LOOP, lastDrawIndex, slices); rho = i * drho; for ( j = 0; j < slices; j++ ) { theta = j * dtheta; x = cos(theta) * sin(rho); y = sin(theta) * sin(rho); z = cos(rho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); imb.glVertex3f(x * radius, y * radius, z * radius); } } // draw slice lines for ( j = 0; j < slices; j++ ) { lastDrawIndex += addDrawCommand(GL_LINE_STRIP, lastDrawIndex, stacks + 1); theta = j * dtheta; for ( i = 0; i <= stacks; i++ ) { rho = i * drho; x = cos(theta) * sin(rho); y = sin(theta) * sin(rho); z = cos(rho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); imb.glVertex3f(x * radius, y * radius, z * radius); } } } else if ( this.drawStyle == GLU_POINT ) { lastDrawIndex += addDrawCommand(GL_POINTS, lastDrawIndex, 2 + (stacks - 2) * slices); // top and bottom-most points if ( normals ) imb.glNormal3f(0.0f, 0.0f, nsign); imb.glVertex3f(0.0f, 0.0f, radius); if ( normals ) imb.glNormal3f(0.0f, 0.0f, -nsign); imb.glVertex3f(0.0f, 0.0f, -radius); // loop over stacks for ( i = 1; i < stacks - 1; i++ ) { rho = i * drho; for ( j = 0; j < slices; j++ ) { theta = j * dtheta; x = cos(theta) * sin(rho); y = sin(theta) * sin(rho); z = cos(rho); if ( normals ) imb.glNormal3f(x * nsign, y * nsign, z * nsign); imb.glVertex3f(x * radius, y * radius, z * radius); } } } return imb.getBuffer(); } private static class DrawCommand { private int mode; private int first; private int count; private DrawCommand(final int mode, final int first, final int count) { this.mode = mode; this.first = first; this.count = count; } void draw() { glDrawArrays(mode, first, count); } } }