/*
* Copyright (C) 2014 MillerV
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package aspect.core;
import aspect.util.Angles;
import aspect.util.Vector2;
import aspect.util.Vector3;
import aspect.render.Canvas;
import aspect.render.ViewModel;
import aspect.entity.Entity;
import aspect.render.Light;
import aspect.resources.modeling.Vertex;
import aspect.util.Color;
import aspect.util.AbstractTransform;
import aspect.util.Matrix4x4;
import aspect.util.Transform;
import aspect.util.Trig;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import static org.lwjgl.opengl.Display.*;
import org.lwjgl.opengl.GL11;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.*;
/**
* This is the utility class used to render the scene. Note that typically
* Entity.draw() should be used to render 3D objects and setCameraViewpoint
* should be used to control the camera.
*
* @author MillerV
*/
public class AspectRenderer {
private static int pushedMatrices;
private static RenderMode mode;
private static FloatBuffer modelViewMatrix;
private static FloatBuffer projectionMatrix;
private static boolean resized = false;
static ViewModel skybox;
public static boolean FBO_ENABLED = false;
/**
* The default camera viewpoint, which uses cameraPos and cameraRot as its
* position and rotation.
*/
public static final Transform DEFAULT_CAMERA = new Transform();
/**
* The current camera viewpoint.
*/
public static AbstractTransform camera = DEFAULT_CAMERA;
/**
* Change between 2D and 3D rendering modes. It is usually not necessary to
* call this method yourself.
*
* @param mode the new RenderMode
*/
public static void setRenderMode(RenderMode mode) {
if (mode == AspectRenderer.mode && !resized) {
return;
}
if (mode == RenderMode.PERSPECTIVE) {
glViewport(0, 0, getWidth(), getHeight());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float aspect = (float) getWidth() / (float) getHeight();
gluPerspective(60, aspect, 0.01f, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_DEPTH_TEST);
glEnable(GL_ALPHA_TEST);
glEnable(GL_LIGHTING);
glDisable(GL_COLOR_MATERIAL);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel(GL_SMOOTH);
glFrontFace(GL_CCW);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
setupCamera();
} else {
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, getCanvasWidth(), 0, getCanvasHeight());
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
resized = false;
AspectRenderer.mode = mode;
}
public static void onDisplayResize() {
resized = true;
}
public static void setCulling(boolean allow) {
if (allow) {
glEnable(GL_CULL_FACE);
} else {
glDisable(GL_CULL_FACE);
}
}
/**
* Clears the color and depth buffers so that the scene can be redrawn. It
* is not necessary to call this method yourself.
*/
public static void clearRenderer() {
clearRenderer(camera);
}
public static void clearRenderer(AbstractTransform viewpoint) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
viewpoint.transformCamera();
Light.drawLights();
}
/**
* Prints the OpenGL version currently supported.
*/
public static void printGlVersion() {
System.out.println("GL VENDOR: " + glGetString(GL_VENDOR));
System.out.println("GL RENDERER: " + glGetString(GL_RENDERER));
System.out.println("GL VERSION: " + glGetString(GL_VERSION));
System.out.println();
}
/**
* Get the height of the window.
*
* @return the window height in pixels
*/
public static int getCanvasHeight() {
return getHeight();
}
/**
* Get the width of the window.
*
* @return the window width in pixels
*/
public static int getCanvasWidth() {
return getWidth();
}
/**
* Get the x coordinate of the left window bound, relative to the left edge
* of the screen.
*
* @return the x coordinate of the window
*/
public static int getCanvasX() {
return getX();
}
/**
* Get the y coordinate of the upper window bound, relative to the upper
* edge of the screen.
*
* @return the y coordinate of the window
*/
public static int getCanvasY() {
return getY();
}
public static void setupCamera() {
camera.transformCamera();
}
/**
* Push a new matrix to the top of the OpenGL matrix stack. It is not
* necessary to call this method yourself if you are using draw3D or
* Entity.draw();
*/
public static void pushMatrix() {
glPushMatrix();
pushedMatrices++;
}
/**
* Pop the matrix from the top of the OpenGL matrix stack. It is not
* necessary to call this method yourself if you are using draw3D or
* Entity.draw(). This method will return false if there is no matrix to
* pop, and will only pop matrices that were pushed using pushMatrix().
*
* @return whether a matrix could be popped.
*/
public static boolean popMatrix() {
if (pushedMatrices > 0) {
glPopMatrix();
pushedMatrices--;
return true;
} else {
return false;
}
}
/**
* Clear the depth buffer, meaning that any new objects will be drawn over
* those already drawn, regardless of their depth.
*/
public static void clearDepthBuffer() {
glClear(GL_DEPTH_BUFFER_BIT);
}
/**
* Save the depth buffer into a IntBuffer, so that it can be restored later
* on.
*
* @return the saved buffer
* @see #restoreDepthBuffer(java.nio.IntBuffer)
*/
public static IntBuffer saveDepthBuffer() {
IntBuffer depth = BufferUtils.createIntBuffer(getWidth() * getHeight());
glReadPixels(0, 0, getWidth(), getHeight(), GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, depth);
return depth;
}
/**
* Restore the depth buffer from a saved IntBuffer.
*
* @param depth the saved buffer
* @see #saveDepthBuffer()
*/
public static void restoreDepthBuffer(IntBuffer depth) {
glRasterPos2i(0, 0);
glDrawPixels(getWidth(), getHeight(), GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, depth);
}
/**
* Clear the color buffer, drawing a black rectangle over the screen but
* keeping the depth buffer, meaning that objects drawn behind those already
* drawn will be hidden.
*/
public static void clearColorBuffer() {
glClear(GL_COLOR_BUFFER_BIT);
}
public static void color(Color color) {
glColor4f(color.red, color.green, color.blue, color.alpha);
}
public static void texCoord(Vector2 texCoord) {
glTexCoord2f(texCoord.x, texCoord.y);
}
public static void normal(Vector3 normal) {
normal = normal.normalize();
glNormal3f(normal.x, normal.y, normal.z);
}
public static void vertex(Vertex vertex) {
color(vertex.color);
texCoord(vertex.texcoord);
normal(vertex.normal);
vertex(vertex.position);
}
public static void vertex(Vector3 vertex) {
glVertex3f(vertex.x, vertex.y, vertex.z);
}
public static void vertex(Vector2 vertex) {
glVertex2f(vertex.x, vertex.y);
}
public static void begin(GeometryType type) {
glBegin(type.glEnum);
}
public static void end() {
glEnd();
}
public static Color getPixelColor(Vector2 p) {
FloatBuffer buff = BufferUtils.createFloatBuffer(3);
glReadPixels((int)p.x, (int)p.y, 1, 1, GL_RGB, GL_FLOAT, buff);
return new Color(buff.get(), buff.get(), buff.get());
}
/**
* Reverts the matrix on top of the OpenGL matrix stack to the identity
* matrix, meaning it will not transform geometry. Any transformations
* applied to matrices further down the stack will still be applied to
* objects.
*/
public static void loadIdentity() {
glLoadIdentity();
}
/**
* Applies a translation transformation to the matrix on top of the OpenGL
* matrix stack.
*
* @param trans the Vector3 to translate by
*/
public static void translate(Vector3 trans) {
glTranslatef(trans.x, trans.y, trans.z);
}
/**
* Applies a translation to the matrix on top of the OpenGL matrix stack.
*
* @param trans the Vector2 to translate by
*/
public static void translate(Vector2 trans) {
glTranslatef(trans.x, trans.y, 0);
}
public static void lookAt(Vector3 position, Vector3 at, Vector3 up) {
gluLookAt(position.x, position.y, position.z, at.x, at.y, at.z, up.x, up.y, up.z);
}
public static void lookAt(Vector3 at, Vector3 up) {
lookAt(Vector3.zero(), at, up);
}
/**
* Applies an x rotation to the matrix on top of the OpenGL matrix stack.
*
* @param angle the angle to rotate by
*/
public static void rotateX(float angle) {
rotate(angle, Vector3.xAxis());
}
/**
* Applies a y rotation to the matrix on top of the OpenGL matrix stack.
*
* @param angle the angle to rotate by
*/
public static void rotateY(float angle) {
rotate(angle, Vector3.yAxis());
}
/**
* Applies a z rotation to the matrix on top of the OpenGL matrix stack.
*
* @param angle the angle to rotate by
*/
public static void rotateZ(float angle) {
rotate(angle, Vector3.zAxis());
}
/**
* Applies a rotation to the matrix on top of the OpenGL matrix stack around
* the given Vector3.
*
* @param angle the angle to rotate by
* @param v the Vector3 to rotate around
*/
public static void rotate(float angle, Vector3 v) {
glRotatef(Trig.toDegrees(angle), v.x, v.y, v.z);
}
/**
* Applies x, y, and z rotations to the matrix on top of the OpenGL matrix
* stack. The rotations are applied in the order y, x, z.
*
* @param angles the angles to rotate by
*/
public static void rotate(Angles angles) {
rotateY(angles.yaw);
rotateX(angles.pitch);
rotateZ(angles.roll);
}
/**
* Applies a scale to the matrix on top of the OpenGL matrix stack.
*
* @param s the scale to use
*/
public static void scale(Vector3 s) {
glScalef(s.x, s.y, s.z);
}
public static void transform(Matrix4x4 mat) {
glMultMatrix(mat.getBuffer());
}
public static Matrix4x4 modelViewMatrix() {
FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
glGetFloat(GL_MODELVIEW_MATRIX, buffer);
return new Matrix4x4(buffer);
}
public static Matrix4x4 projectionMatrix() {
FloatBuffer mat = BufferUtils.createFloatBuffer(16);
glGetFloat(GL_PROJECTION_MATRIX, mat);
return new Matrix4x4(mat);
}
public static IntBuffer viewport() {
IntBuffer buff = BufferUtils.createIntBuffer(16);
glGetInteger(GL_VIEWPORT, buff);
return buff;
}
/**
* Draw an Entity at its position and angle, disregarding the current camera
* position so that the object is drawn relative to the camera.
*
* @param ent the Entity to draw
*/
public static void draw3DAtCamera(Entity ent) {
loadIdentity();
ent.render();
setupCamera();
}
/**
* Draw a 3D ViewModel at the given position and angles, disregarding the
* current camera position so that the object is drawn relative to the
* camera.
*
* @param mdl the ViewModel to draw
*/
public static void draw3DAtCamera(ViewModel mdl) {
loadIdentity();
mdl.render();
setupCamera();
}
/**
* Draw a Canvas2D on the screen at the given position. The given point is
* the lower-left coordinate of the canvas, relative to the lower-left
* corner of the screen.
*
* @param canvas the Canvas2D to draw
* @param transform the model transform
*/
public static void draw2D(Canvas canvas, Transform transform) {
pushMatrix();
transform.transformModel();
canvas.getModel().render();
popMatrix();
}
/**
* Add fog to the world, making objects further away appear less visible.
* This does not increase the performance, but allows you to avoid drawing
* objects that will not be visible to the user.
*
* @param density the density of the fog
* @param color the color of the fog
* @param mode the mode to use for the fog (1 fades slowly, 2 fades quickly)
* @see #removeFog()
*/
public static void makeFog(float density, Color color, int mode) {
glEnable(GL_FOG);
if (mode == 1) {
glFogi(GL_FOG_MODE, GL_EXP);
} else if (mode == 2) {
glFogi(GL_FOG_MODE, GL_EXP2);
}
glFogf(GL_FOG_DENSITY, density);
FloatBuffer fb = color.getBuffer();
glFog(GL_FOG_COLOR, fb);
glHint(GL_FOG_HINT, GL_DONT_CARE);
}
/**
* Set the ambient light of the scene. Pixels will have their color values
* multiplied by the values of the given Color before being drawn.
*
* @param color the light color
*/
public static void setAmbientLight(Color color) {
FloatBuffer light = color.getBuffer();
glLightModel(GL_LIGHT_MODEL_AMBIENT, light);
}
public static void setLighting(boolean lighting) {
if (lighting) {
glEnable(GL_LIGHTING);
} else {
glDisable(GL_LIGHTING);
}
}
/**
* Remove any fog that has been created by makeFog.
*/
public static void removeFog() {
glDisable(GL_FOG);
}
/**
* Set the background color. For the best results, you should make the
* background the same color as the fog.
*
* @param color the color to set the background to
*/
public static void setBackground(Color color) {
glClearColor(color.red, color.green, color.blue, color.alpha);
}
public static void addSkybox(ViewModel model) {
skybox = model;
}
/**
* Set the current camera viewpoint to the default camera, which is
* controlled by the camera functions.
*
* @see #setCameraViewpoint(engine.core.CameraViewpoint)
*/
public static void useDefaultCamera() {
camera = DEFAULT_CAMERA;
}
/**
* The set of supported render modes.
*/
public static enum RenderMode {
PERSPECTIVE, ORTHOGRAPHIC
}
public static enum GeometryType {
POINTS(GL_POINTS),
LINES(GL_LINES),
LINE_STRIP(GL_LINE_STRIP),
LINE_LOOP(GL_LINE_LOOP),
POLYGON(GL_POLYGON),
QUADS(GL_QUADS),
QUAD_STRIP(GL_QUAD_STRIP),
TRIANGLES(GL_TRIANGLES),
TRIANGLE_STRIP(GL_TRIANGLE_STRIP),
TRIANGLE_FAN(GL_TRIANGLE_FAN);
public final int glEnum;
GeometryType(int glEnum) {
this.glEnum = glEnum;
}
}
}