/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'Shaven Puppy' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.shavenpuppy.jglib.sprites;
import java.util.ArrayList;
import org.lwjgl.util.ReadableColor;
import org.lwjgl.util.ReadablePoint;
import org.lwjgl.util.vector.ReadableVector2f;
import com.shavenpuppy.jglib.opengl.GLRenderable;
import com.shavenpuppy.jglib.util.FloatList;
import com.shavenpuppy.jglib.util.ShortList;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
/**
* Renders arbitrary OpenGL geometry using VBOs.
*/
public abstract class GeometryStyle extends AbstractStyle implements SimpleRenderer {
/** Geometry runs */
private final ArrayList<GeometryRun> geometry = new ArrayList<GeometryRun>(1);
/** The geometry data, which describes a list of vertices, and a list of indices */
private transient GeometryData data;
/** Vertices: this is the vertex data from the {@link #data} member */
private transient FloatList vertices;
/** Number of vertices */
private transient short numVertices;
/** Indices: this is the index data from the {@link #data} member */
private transient ShortList indices;
/** Current u/v coordinates */
private transient float u, v;
/** Current color (RGBA packed) */
private transient int c;
/** Whether the geometry specified has color and/or texture coordinates */
private transient boolean hasColor, hasTexture;
/** The last bit of RenderableGeometry we did */
private transient MeshGeometry previous;
/**
* Describes either a "renderable" (arbitrary OpenGL commands) or a call to glDrawRangeElements that will
* draw geometry specified in the geometry data.
*/
private interface GeometryRun {
/**
* Render using the given vertex offset and index offset
* @param vertexOffset
* @param indexOffset
*/
void render(int vertexOffset, int indexOffset);
}
private static class RenderableGeometry implements GeometryRun {
/** Renderable command */
GLRenderable renderable;
/**
* C'tor
* @param renderable
*/
public RenderableGeometry(GLRenderable renderable) {
this.renderable = renderable;
}
@Override
public void render(int vertexOffset, int indexOffset) {
renderable.render();
}
}
private class MeshGeometry implements GeometryRun {
int primitiveType;
int numIndices;
int offset;
/**
* C'tor
* @param primitiveType
* @param startVertex
* @param endVertex
* @param numIndices
*/
public MeshGeometry(int primitiveType, int offset, int numIndices) {
this.primitiveType = primitiveType;
this.numIndices = numIndices;
this.offset = offset;
}
@Override
public void render(int vertexOffset, int indexOffset) {
glDrawRangeElements(primitiveType, vertexOffset, vertexOffset + numVertices, numIndices, GL_UNSIGNED_SHORT, offset + indexOffset);
}
}
public GeometryStyle() {
}
@Override
public final boolean getRenderSprite() {
return false;
}
@Override
public final int getStyleID() {
return hashCode();
}
@Override
public final GeometryData build() {
// Write the geometry to the buffer
if (data == null) {
data = new GeometryData(new FloatList(true, 128), new ShortList(true, 128));
vertices = data.getVertexData();
indices = data.getIndexData();
}
// TODO: optimise for static geometry - don't clear away everything, only call render() once, etc.
data.clear();
numVertices = 0;
render();
return data;
}
@Override
public final void setupState() {
}
@Override
public final void resetState() {
}
/**
* Override to make calls to the various gl* methods below
*/
protected abstract void render();
@Override
public void glRender(GLRenderable renderable) {
geometry.add(new RenderableGeometry(renderable));
// Stop continuation
previous = null;
}
private static boolean isContinuable(int type) {
return type == GL_TRIANGLES || type == GL_QUADS || type == GL_POINTS || type == GL_LINES;
}
@Override
public void glRender(final int primitiveType, final short[] indices) {
if (indices.length == 0) {
return;
}
// If the last GeometryRun was the same type and is continuable, we'll extend it instead of doing a new one
MeshGeometry gr;
if (previous != null && previous.primitiveType == primitiveType && isContinuable(primitiveType)) {
gr = previous;
gr.numIndices += indices.length;
} else {
gr = new MeshGeometry(primitiveType, this.indices.size() * 2, indices.length);
geometry.add(gr);
previous = gr;
}
this.indices.addAll(indices);
}
@Override
public short glVertex2f(float x, float y) {
vertices.add(x);
vertices.add(y);
vertices.add(u);
vertices.add(v);
vertices.add(Float.intBitsToFloat(c));
return numVertices ++;
}
@Override
public short getVertexOffset() {
return numVertices;
}
@Override
public short glVertex(ReadablePoint vertex) {
return glVertex2f(vertex.getX(), vertex.getY());
}
@Override
public short glVertex(ReadableVector2f vertex) {
return glVertex2f(vertex.getX(), vertex.getY());
}
@Override
public void glTexCoord2f(float u, float v) {
this.u = u;
this.v = v;
hasTexture = true;
}
@Override
public void glColori(int color) {
this.c = color;
hasColor = true;
}
@Override
public void glColor4ub(byte r, byte g, byte b, byte a) {
this.c = ((a << 24) & 0xFF000000) | ((b << 16) & 0xFF0000) | ((g << 8) & 0xFF00) | (r & 0xFF);
hasColor = true;
}
@Override
public void glColor3ub(byte r, byte g, byte b) {
glColor4ub(r, g, b, (byte) 255);
}
@Override
public void glColor4f(float r, float g, float b, float a) {
glColor4ub((byte) (r * 255.0f), (byte) (g * 255.0f), (byte) (b * 255.0f), (byte) (a * 255.0f));
}
@Override
public void glColor3f(float r, float g, float b) {
glColor4f(r, g, b, 1.0f);
}
@Override
public void glColor(ReadableColor color) {
glColor4ub(color.getRedByte(), color.getGreenByte(), color.getBlueByte(), color.getAlphaByte());
}
@Override
public final void render(int vertexOffset, int indexOffset) {
glEnableClientState(GL_VERTEX_ARRAY);
if (hasColor) {
glEnableClientState(GL_COLOR_ARRAY);
} else {
glDisableClientState(GL_COLOR_ARRAY);
}
if (hasTexture) {
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
} else {
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
int n = geometry.size();
for (int i = 0; i < n; i++) {
GeometryRun run = geometry.get(i);
run.render(vertexOffset, indexOffset * 2);
}
geometry.clear();
previous = null;
if (hasColor) {
hasColor = false;
}
if (hasTexture) {
hasTexture = false;
}
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
// glDisableClientState(GL_VERTEX_ARRAY);
}
}