package net.krazyweb.opengl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import net.krazyweb.renderer.Color;
import net.krazyweb.renderer.Renderer;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;
import org.newdawn.slick.opengl.TextureLoader;
public class OpenGL implements Renderer {
private static final int SIZE_OF_FLOAT = 4;
private Color clearColor = Color.WHITE;
private HashMap<Integer, OpenGLMesh> meshes = new HashMap<Integer, OpenGLMesh>();
private OpenGLMesh currentMesh;
private HashMap<Integer, OpenGLFBO> fbos = new HashMap<Integer, OpenGLFBO>();
private OpenGLFBO currentFBO;
private int currentShader;
private int meshCount = 0;
private int fboCount = 0;
private int activeTextureUnit = -1;
private int width, height;
private int frames = 0;
private long lastFPS = 0;
private ArrayList<Float> vertexList;
private ArrayList<Integer> indexList;
private ArrayList<Float> colorList;
private ArrayList<Float> textureList;
@Override
public void setClearColor(Color color) {
clearColor = color;
GL11.glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
}
@Override
public void createContext(int width, int height) {
try {
Display.setDisplayMode(new DisplayMode(width, height));
Display.create(new PixelFormat(4, 8, 0));
GL11.glViewport(0, 0, width, height);
GL11.glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
GL11.glClearDepth(1.0f);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glDepthFunc(GL11.GL_LEQUAL);
GL11.glEnable(GL11.GL_CULL_FACE);
lastFPS = (System.nanoTime() / 1000000);
this.width = width;
this.height = height;
} catch (LWJGLException e) {
System.err.println("Could not create OpenGL context!");
e.printStackTrace();
}
activeTextureUnit = GL13.GL_TEXTURE0;
}
@Override
public void update() {
Display.update();
if ((System.nanoTime() / 1000000) - lastFPS > 1000) {
System.out.println("FPS: " + frames);
frames = 0; //reset the FPS counter
lastFPS += 1000; //add one second
}
frames++;
}
@Override
public void syncFrames(int frameRate) {
Display.sync(frameRate);
}
@Override
public boolean isClosed() {
return Display.isCloseRequested();
}
@Override
public void destroy() {
Display.destroy();
}
@Override
public int beginMesh() {
meshCount++;
currentMesh = new OpenGLMesh();
meshes.put(meshCount, currentMesh);
vertexList = new ArrayList<Float>();
indexList = new ArrayList<Integer>();
colorList = new ArrayList<Float>();
textureList = new ArrayList<Float>();
return meshCount;
}
@Override
public int addVertexToMesh(Vector3f point) {
return addVertexToMesh(point, new Vector3f(0.0f, 0.0f, 0.0f), new Vector2f(0.0f, 0.0f), Color.WHITE);
}
@Override
public int addVertexToMesh(Vector3f point, Color color) {
return addVertexToMesh(point, new Vector3f(0.0f, 0.0f, 0.0f), new Vector2f(0.0f, 0.0f), color);
}
@Override
public int addVertexToMesh(Vector3f point, Vector2f texture) {
return addVertexToMesh(point, new Vector3f(0.0f, 0.0f, 0.0f), texture, Color.WHITE);
}
@Override
public int addVertexToMesh(Vector3f point, Vector3f normal, Color color) {
return addVertexToMesh(point, normal, new Vector2f(0.0f, 0.0f), color);
}
@Override
public int addVertexToMesh(Vector3f point, Vector2f texture, Color color) {
return addVertexToMesh(point, new Vector3f(0.0f, 0.0f, 0.0f), texture, color);
}
@Override
public int addVertexToMesh(Vector3f point, Vector3f normal, Vector2f texture, Color color) {
colorList.add(color.r);
colorList.add(color.g);
colorList.add(color.b);
colorList.add(color.a);
textureList.add(texture.x);
textureList.add(texture.y);
vertexList.add(point.x);
vertexList.add(point.y);
vertexList.add(point.z);
vertexList.add(normal.x);
vertexList.add(normal.y);
vertexList.add(normal.z);
return (vertexList.size() / 6) - 1;
}
@Override
public void addTriangleToMesh(int vertex1, int vertex2, int vertex3) {
indexList.add(vertex1);
indexList.add(vertex2);
indexList.add(vertex3);
}
@Override
public void finishMesh() {
FloatBuffer vertices;
IntBuffer indices;
FloatBuffer colors;
FloatBuffer textures;
float[] vertexArray = new float[vertexList.size()];
for (int i = 0; i < vertexList.size(); i++) {
Float f = vertexList.get(i);
vertexArray[i] = (f != null ? f : Float.NaN);
}
vertices = BufferUtils.createFloatBuffer(vertexArray.length);
vertices.put(vertexArray);
vertices.flip();
int[] indexArray = new int[indexList.size()];
for (int i = 0; i < indexList.size(); i++) {
Integer f = indexList.get(i);
indexArray[i] = (f != null ? f : 0);
}
indices = BufferUtils.createIntBuffer(indexArray.length);
indices.put(indexArray);
indices.flip();
currentMesh.numberOfIndices = indices.capacity();
float[] textureArray = new float[textureList.size()];
for (int i = 0; i < textureList.size(); i++) {
Float f = textureList.get(i);
textureArray[i] = f != null ? f : Float.NaN;
}
textures = BufferUtils.createFloatBuffer(textureArray.length);
textures.put(textureArray);
textures.flip();
float[] colorArray = new float[colorList.size()];
for (int i = 0; i < colorList.size(); i++) {
Float f = colorList.get(i);
colorArray[i] = (f != null ? f : Float.NaN);
}
colors = BufferUtils.createFloatBuffer(colorArray.length);
colors.put(colorArray);
colors.flip();
int[] temp = new int[4];
temp[0] = GL15.glGenBuffers();
temp[1] = GL15.glGenBuffers();
temp[2] = GL15.glGenBuffers();
temp[3] = GL15.glGenBuffers();
currentMesh.VAO = GL30.glGenVertexArrays();
GL30.glBindVertexArray(currentMesh.VAO);
currentMesh.VBOVertices = temp[0];
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, currentMesh.VBOVertices);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 6 * SIZE_OF_FLOAT, 0);
GL20.glVertexAttribPointer(3, 3, GL11.GL_FLOAT, false, 6 * SIZE_OF_FLOAT, 3 * SIZE_OF_FLOAT);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
currentMesh.VBOIndices = temp[1];
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, currentMesh.VBOIndices);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indices, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
currentMesh.VBOColors = temp[2];
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, currentMesh.VBOColors);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, colors, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(1, 4, GL11.GL_FLOAT, false, 0, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
currentMesh.VBOTextures = temp[3];
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, currentMesh.VBOTextures);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, textures, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(2, 2, GL11.GL_FLOAT, false, 0, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
/*currentMesh.vertexList = vertexList;
currentMesh.textureList = textureList;
currentMesh.indexList = indexList;
currentMesh.colorList = colorList;*/
vertexList = null;
indexList = null;
colorList = null;
textureList = null;
currentMesh = null;
}
@Override
public void deleteMeshBuffers() {
GL30.glDeleteVertexArrays(currentMesh.VAO);
GL15.glDeleteBuffers(currentMesh.VBOVertices);
GL15.glDeleteBuffers(currentMesh.VBOIndices);
GL15.glDeleteBuffers(currentMesh.VBOColors);
GL15.glDeleteBuffers(currentMesh.VBOTextures);
currentMesh.numberOfIndices = 0;
}
@Override
public void deleteMesh(int meshID) {
if (!meshes.containsKey(meshID)) {
return;
}
currentMesh = meshes.get(meshID);
GL30.glDeleteVertexArrays(currentMesh.VAO);
GL15.glDeleteBuffers(currentMesh.VBOVertices);
GL15.glDeleteBuffers(currentMesh.VBOIndices);
GL15.glDeleteBuffers(currentMesh.VBOColors);
GL15.glDeleteBuffers(currentMesh.VBOTextures);
currentMesh.numberOfIndices = 0;
meshes.remove(meshID);
currentMesh = null;
}
@Override
public void batchMesh(int receivingMesh, int meshToBatch, Vector3f batchTranslation, boolean deleteMeshToBatch) {
OpenGLMesh target = meshes.get(receivingMesh);
OpenGLMesh toCombine = meshes.get(meshToBatch);
int high = 0;
for (int x : target.indexList) {
if (x > high) {
high = x;
}
}
high += 1;
currentMesh = target;
deleteMeshBuffers();
for (int i = 0; i < toCombine.indexList.size(); i++) {
toCombine.indexList.set(i, toCombine.indexList.get(i) + high);
}
for (int i = 0; i < toCombine.vertexList.size(); i++) {
if (i % 3 == 0) {
toCombine.vertexList.set(i, toCombine.vertexList.get(i) + batchTranslation.x);
} else if (i % 3 == 1) {
toCombine.vertexList.set(i, toCombine.vertexList.get(i) + batchTranslation.y);
} else {
toCombine.vertexList.set(i, toCombine.vertexList.get(i) + batchTranslation.z);
}
}
target.vertexList.addAll(toCombine.vertexList);
target.indexList.addAll(toCombine.indexList);
target.colorList.addAll(toCombine.colorList);
target.textureList.addAll(toCombine.textureList);
vertexList = target.vertexList;
indexList = target.indexList;
colorList = target.colorList;
textureList = target.textureList;
finishMesh();
if (deleteMeshToBatch) {
currentMesh = toCombine;
deleteMeshBuffers();
meshes.remove(toCombine);
currentMesh = null;
}
}
@Override
public void drawMesh(int meshID) {
OpenGLMesh mesh = meshes.get(meshID);
GL30.glBindVertexArray(mesh.VAO);
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
GL20.glEnableVertexAttribArray(3);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, mesh.VBOIndices);
GL11.glDrawElements(GL11.GL_TRIANGLES, mesh.numberOfIndices, GL11.GL_UNSIGNED_INT, 0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL20.glDisableVertexAttribArray(3);
GL30.glBindVertexArray(0);
}
@Override
public int getQuad(float x, float y, float z, float width, float height) {
int mesh = beginMesh();
int v1 = addVertexToMesh(new Vector3f(x, y, z), new Vector2f(0.0f, 0.0f));
int v2 = addVertexToMesh(new Vector3f(x + width, y, z), new Vector2f(1.0f, 0.0f));
int v3 = addVertexToMesh(new Vector3f(x + width, y + height, z), new Vector2f(1.0f, 1.0f));
int v4 = addVertexToMesh(new Vector3f(x, y + height, z), new Vector2f(0.0f, 1.0f));
addTriangleToMesh(v1, v2, v3);
addTriangleToMesh(v1, v3, v4);
finishMesh();
return mesh;
}
@Override
public void setMatrix(Matrix4f matrix) {
FloatBuffer matrix44Buffer = BufferUtils.createFloatBuffer(16);
matrix.store(matrix44Buffer); matrix44Buffer.flip();
GL20.glUniformMatrix4(GL20.glGetUniformLocation(currentShader, "mvpMatrix"), false, matrix44Buffer);
}
@Override
public int createShader(String vertexShader, String fragmentShader) {
int vertShader = 0, fragShader = 0;
int shaderID = GL20.glCreateProgram();
boolean useShader = true;
if (shaderID != 0) {
vertShader = loadShader(vertexShader, GL20.GL_VERTEX_SHADER);
fragShader = loadShader(fragmentShader, GL20.GL_FRAGMENT_SHADER);
} else useShader = false;
if (vertShader != 0 && fragShader != 0) {
GL20.glAttachShader(shaderID, vertShader);
GL20.glAttachShader(shaderID, fragShader);
GL20.glLinkProgram(shaderID);
GL20.glBindAttribLocation(shaderID, 0, "Position");
GL20.glBindAttribLocation(shaderID, 1, "Color");
GL20.glValidateProgram(shaderID);
} else useShader = false;
if (!useShader) {
System.exit(-1);
}
return shaderID;
}
private int loadShader(String filename, int type) {
String shaderCode = "";
String line;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(OpenGL.class.getClassLoader().getResourceAsStream(filename)));
while ((line = reader.readLine()) != null) {
shaderCode += line + "\n";
}
} catch (IOException e) {
System.out.println("Failed reading shader file: " + filename);
return 0;
}
int shaderID = GL20.glCreateShader(type);
GL20.glShaderSource(shaderID, shaderCode);
GL20.glCompileShader(shaderID);
if (GL20.glGetShader(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE){
System.out.println("Shader [" + filename + "] not compiled!");
int maxLength = GL20.glGetShader(shaderID, GL20.GL_INFO_LOG_LENGTH);
System.out.println(GL20.glGetShaderInfoLog(shaderID, maxLength));
shaderID = 0;
}
return shaderID;
}
@Override
public void useShader(int shader) {
GL20.glUseProgram(shader);
currentShader = shader;
}
@Override
public void setShaderProperty(String property, Color color) {
GL20.glUniform4f(GL20.glGetUniformLocation(currentShader, property), color.r, color.g, color.b, color.a);
}
@Override
public void setShaderProperty(String property, int value) {
int location = GL20.glGetUniformLocation(currentShader, property);
GL20.glUniform1i(location, value);
}
@Override
public void setShaderProperty(String property, float value) {
int location = GL20.glGetUniformLocation(currentShader, property);
GL20.glUniform1f(location, value);
}
@Override
public void setShaderProperty(String property, Vector2f value) {
int location = GL20.glGetUniformLocation(currentShader, property);
GL20.glUniform2f(location, value.x, value.y);
}
@Override
public void clear() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
@Override
public int loadTexture(String texture, TEXTURE_FORMAT format, TEXTURE_HINT hint) {
try {
if (hint == TEXTURE_HINT.BILINEAR) {
return TextureLoader.getTexture(format.type, OpenGL.class.getClassLoader().getResourceAsStream(texture), true).getTextureID();
} else {
return TextureLoader.getTexture(format.type, OpenGL.class.getClassLoader().getResourceAsStream(texture), true, GL11.GL_NEAREST).getTextureID();
}
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
@Override
public void bindTexture(int texture, int textureUnit) {
if (activeTextureUnit != GL13.GL_TEXTURE0 + textureUnit) {
GL13.glActiveTexture((activeTextureUnit = GL13.GL_TEXTURE0 + textureUnit));
}
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
@Override
public void unbindTexture(int textureUnit) {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + textureUnit);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
@Override
public void enableBlending(BLEND_MODE blendMode) {
GL11.glEnable(GL11.GL_BLEND);
switch (blendMode) {
case ADDITIVE:
GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
break;
case TRANSPARENCY:
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
break;
}
}
@Override
public void disableBlending() {
GL11.glDisable(GL11.GL_BLEND);
}
@Override
public int beginFBO(int width, int height, int numberOfTextures) {
fboCount++;
currentFBO = new OpenGLFBO(width, height, numberOfTextures);
fbos.put(fboCount, currentFBO);
return fboCount;
}
@Override
public void addTextureToFBO(TEXTURE_TYPE type) {
currentFBO.addTexture(type);
}
@Override
public void finishFBO() {
currentFBO.finalize();
currentFBO = null;
}
@Override
public void enableFBO(int fbo) {
fbos.get(fbo).enable();
}
@Override
public void disableFBO() {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GL11.glViewport(0, 0, width, height);
}
@Override
public int getFBOTexture(int fbo, int texture) {
return fbos.get(fbo).textures[texture];
}
}