kubex.KubexGame.java Source code

Java tutorial

Introduction

Here is the source code for kubex.KubexGame.java

Source

package kubex;

import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_NEAREST;
import static org.lwjgl.opengl.GL11.GL_RGB;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.GL_TEXTURE1;
import static org.lwjgl.opengl.GL13.GL_TEXTURE2;
import static org.lwjgl.opengl.GL13.GL_TEXTURE3;
import static org.lwjgl.opengl.GL13.GL_TEXTURE4;
import static org.lwjgl.opengl.GL13.GL_TEXTURE5;
import static org.lwjgl.opengl.GL13.GL_TEXTURE6;
import static org.lwjgl.opengl.GL13.GL_TEXTURE7;
import static org.lwjgl.opengl.GL13.GL_TEXTURE8;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glBufferSubData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT0;
import static org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT1;
import static org.lwjgl.opengl.GL30.GL_COLOR_ATTACHMENT2;
import static org.lwjgl.opengl.GL30.GL_COMPARE_REF_TO_TEXTURE;
import static org.lwjgl.opengl.GL30.GL_DEPTH_ATTACHMENT;
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER;
import static org.lwjgl.opengl.GL30.GL_RGB32F;
import static org.lwjgl.opengl.GL30.glBindFramebuffer;
import static org.lwjgl.opengl.GL30.glBindRenderbuffer;
import static org.lwjgl.opengl.GL30.glFramebufferRenderbuffer;
import static org.lwjgl.opengl.GL30.glGenFramebuffers;
import static org.lwjgl.opengl.GL30.glGenRenderbuffers;
import static org.lwjgl.opengl.GL30.glRenderbufferStorage;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Scanner;

import javax.swing.JOptionPane;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.util.vector.Matrix4f;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.TextureLoader;
import org.newdawn.slick.util.ResourceLoader;

import ivengine.IvEngine;
import ivengine.Util;
import ivengine.properties.Cleanable;
import ivengine.view.Camera;
import ivengine.view.CameraInverseProjEnvelope;
import ivengine.view.MatrixHelper;
import kubex.gui.Chunk;
import kubex.gui.DepthPeelingLiquidRenderer;
import kubex.gui.FinalDrawManager;
import kubex.gui.GlobalTextManager;
import kubex.gui.Hud;
import kubex.gui.LiquidRenderer;
import kubex.gui.ShadowsManager;
import kubex.gui.Sky;
import kubex.gui.World;
import kubex.menu.MonecruftMenu;
import kubex.shaders.DeferredNoReflectionsShaderProgram;
import kubex.shaders.DeferredReflectionsShaderProgram;
import kubex.shaders.DeferredShaderProgram;
import kubex.shaders.DeferredTerrainShaderProgram;
import kubex.shaders.DeferredTerrainUnshadowShaderProgram;
import kubex.shaders.DeferredUnderwaterFinalShaderProgram;
import kubex.shaders.DeferredUnderwaterTerrainShaderProgram;
import kubex.shaders.DeferredUnderwaterUnshadowTerrainShaderProgram;
import kubex.shaders.DepthVoxelShaderProgram;
import kubex.shaders.HudShaderProgram;
import kubex.shaders.TerrainVoxelShaderProgram;
import kubex.shaders.VoxelShaderProgram;
import kubex.storage.ByteArrayPool;
import kubex.storage.FileManager;
import kubex.storage.FloatBufferPool;
import kubex.utils.InputHandler;
import kubex.utils.KubexException;
import kubex.utils.KubexGPUException;
import kubex.utils.KubexIOException;
import kubex.utils.TimeManager;

/**
 * This work is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/.
 * 
 * @author Vctor Arellano Vicente (Ivelate)
 * 
 * Main class. Manages all OpenGL low level work, like texture switching, rendering passes, shaders, etc.
 * Contains both the main render loop and the main update loop. Inits the OpenGL main window.
 */
public class KubexGame implements Cleanable {
    public static final int[] TEXTURE_FETCH = { GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_TEXTURE3, GL_TEXTURE4,
            GL_TEXTURE5, GL_TEXTURE6, GL_TEXTURE7, GL_TEXTURE8 };
    public static final int TILES_TEXTURE_LOCATION = 0; //Cube textures will be located on GL_TEXTURE0
    public static final int CURRENT_LIQUID_NORMAL_TEXTURE_LOCATION = 8; //The first water layer normal texture will be located on GL_TEXTURE8
    public static final int BASEFBO_NORMALS_BRIGHTNESS_TEXTURE_LOCATION = 1; //The texture which contains both the normal (Compressed) and the lighting of the first deferred rendering pass
    //will be located on GL_TEXTURE1
    public static final int BASEFBO_COLOR_TEXTURE_LOCATION = 2; //The texture which contains the color of the first deferred rendering pass will be located on GL_TEXTURE2
    public static final int BASEFBO_DEPTH_TEXTURE_LOCATION = 3; //The texture which contains the depth of the first deferred rendering pass will be located on GL_TEXTURE3
    public static final int SHADOW_TEXTURE_LOCATION = 4; //The texture array which contains the shadow maps for each shadow cascade will be located on GL_TEXTURE4
    public static final int LIQUIDLAYERS_TEXTURE_LOCATION = 5; //The texture array which contains the liquid layers depth (For each liquid depth) will be located on GL_TEXTURE5
    public static final int DEFERREDFBO_COLOR_TEXTURE_LOCATION = 6; //The texture which contains the final color from the second deferred rendering pass will be located on GL_TEXTURE6
    public static final int WATER_NORMAL_TEXTURE_LOCATION = 7; //The texture which contains the normal water perturbation texture LOADED FROM DISK will be located on GL_TEXTURE7

    private KubexSettings settings;

    private int X_RES = 1400; //Window width. Uses settings width
    private int Y_RES = 900; //Window height. Uses settings width
    private int SHADOW_XRES = 2048; //Default shadow resolution for each shadow map. Per default, 2048x2048
    private int SHADOW_YRES = 2048;
    private final int SHADOW_LAYERS = 4; //Number of shadow layers used. The value is forced to 4
    private int LIQUID_LAYERS; //The number of water layers being depth sorted. Uses the settings value

    private final float CAMERA_NEAR = 0.1f; //The camera near will be placed to 0.1m from the camera
    private final float CAMERA_FAR; //The camera far varies in function of the rendering distance specified in settings

    private final float[] SHADOW_SPLITS; //Split distances of the frustrum, used for cascaded shadow mapping

    //Shaders
    private TerrainVoxelShaderProgram VSP;
    private HudShaderProgram HSP;
    private DepthVoxelShaderProgram DVSP;
    private DeferredShaderProgram DTSP;
    private DeferredShaderProgram DUTSP;
    private DeferredShaderProgram DUFSP;
    private DeferredShaderProgram DRSP;

    private TimeManager TM; //Manages the time elapsed
    private Camera cam;
    private CameraInverseProjEnvelope camInvProjEnv; //Default camera
    private Camera sunCam; //Sun camera

    //Each one of this classes are very important and its recommended to look at its description
    private World world;
    private Hud hud;
    private Sky sky;
    private GlobalTextManager textManager;
    private ShadowsManager shadowsManager;
    private LiquidRenderer liquidRenderer;
    private FinalDrawManager finalDrawManager;
    private FileManager fileManager;

    private int tilesTexture;
    private Texture nightDomeTexture; //The nightdome texture will be uploaded separately, using the Slick-Utils library. It will be switched with the tiles texture, so binding one or another
    //depending on the rendering needs is neccesary

    private boolean hasFocus = true; //If the screen has focus
    private Thread shutdownHook; //If the program is closed via a way other than ESC, this thread will execute before app is closed

    private int baseFbo; //Default FrameBufferObject. Used for the default first pass draw
    private int deferredFbo; //Second pass deferred rendering fbo
    private int[] shadowFbos; //Each shadow map will have one separate fbo
    //Water layers fbos are not stored here. Third pass deferred rendering fbo will be the default fbo (0), which renders to screen

    public KubexGame(KubexSettings settings) throws LWJGLException, KubexException {
        this.settings = settings;
        LIQUID_LAYERS = this.settings.WATER_LAYERS;

        if (settings.FULLSCREEN_ENABLED) { //If fullscreen is enabled, sets it on on the desktop resolution and adapts X_RES and Y_RES
            DisplayMode dm = Display.getDesktopDisplayMode();
            X_RES = dm.getWidth();
            Y_RES = dm.getHeight();
            IvEngine.configDisplay(dm, "Monecruft", true, false, true);
        } else { //Windowed mode
            this.X_RES = settings.WINDOW_XRES;
            this.Y_RES = settings.WINDOW_YRES;
            IvEngine.configDisplay(X_RES, Y_RES, "Monecruft", true, false, false);
        }

        //"GPU not supported" error handling
        if (!GLContext.getCapabilities().OpenGL32)
            throw new KubexGPUException("Your GPU doesn't support OpenGL 3.2");
        else if (GL11.glGetInteger(GL20.GL_MAX_TEXTURE_IMAGE_UNITS) < 8)
            throw new KubexGPUException(
                    "Your GPU doesn't support 8 texture units, and Kubex needs them to run correctly");

        Thread.currentThread().setPriority(Thread.currentThread().getPriority() + 1); //Faster than the others -> game experience > loading

        //Sets the camera far distance in function of the maximum render distance (Considering height is fixed as 8x32 m)
        CAMERA_FAR = (float) Math
                .sqrt(World.HEIGHT * World.HEIGHT + settings.RENDER_DISTANCE * settings.RENDER_DISTANCE)
                * Chunk.CHUNK_DIMENSION;
        float[] ssplits = ShadowsManager.calculateSplits(1f, CAMERA_FAR, SHADOW_LAYERS, 0.3f);
        ssplits[0] = CAMERA_NEAR;
        ssplits[1] = ssplits[1] / 4;
        ssplits[2] = ssplits[2] / 2.5f;
        ssplits[3] = ssplits[3] / 1.5f;

        SHADOW_SPLITS = ssplits;

        try {
            initResources(); //Inits the textures, shaders, objects, etc
        } catch (IOException e) {
            throw new KubexIOException("Error reading/storing files in disk");
        } catch (KubexIOException e) {
            throw e;
        }

        //Main loop
        boolean running = true;
        Mouse.setGrabbed(true); //Grabs the mouse

        shutdownHook = new Thread() { //If the app is closed unusually, safelly terminates it anyways (Saving chunks in disk)
            @Override
            public void run() {
                closeApp();
            }
        };
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        TM.getDeltaTime();
        try {
            while (running && !Display.isCloseRequested()) { //Game loop
                Display.isActive();
                while (Keyboard.next()) {
                    handleKeyboardInput();
                }
                while (Mouse.next()) {
                    handleMouseInput();
                }
                int wheel = Mouse.getDWheel();
                if (wheel != 0) {
                    wheel = wheel > 0 ? 1 : -1;
                    InputHandler.addWheel(wheel);
                }
                if (InputHandler.isESCPressed()) {
                    running = false;
                    InputHandler.setESC(false);
                }
                update(TM.getDeltaTime());
                render();
                if (Display.isActive() && !hasFocus) {
                    hasFocus = true;
                    Mouse.setGrabbed(true);
                } else if (!Display.isActive() && hasFocus) //If window loses focus, ungrab the mouse
                {
                    hasFocus = false;
                    Mouse.setGrabbed(false);
                }
                // Flip the buffers and sync to 60 FPS
                Display.update();
                Display.sync(60);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        closeApp();
    }

    /**
     * Update loop
     */
    private void update(float delta) {
        TM.updateFPS();
        if (delta > 0.2)
            delta = 0.2f;
        Display.setTitle("FPS: " + TM.getFPS());
        //Update cam pos
        this.world.update(delta);

        this.sky.update(delta);

        this.textManager.update(delta);
    }

    /**
     * Render loop
     */
    private void render() {
        if (this.settings.SHADOWS_ENABLED) //If shadows are enabled, calculate each cascade shadow map
        {
            this.shadowsManager.calculateCascadeShadows(this.sunCam.getViewMatrix(),
                    this.world.getWorldCornersLow(), this.world.getWorldCornersHigh());
            this.world.overrideCurrentShader(this.DVSP);

            for (int i = 0; i < this.shadowsManager.getNumberSplits(); i++) {
                glBindFramebuffer(GL_FRAMEBUFFER, this.shadowFbos[i]);
                glClear(GL11.GL_DEPTH_BUFFER_BIT);
                GL11.glViewport(0, 0, SHADOW_XRES, SHADOW_YRES);
                this.world.overrideCurrentPVMatrix(this.shadowsManager.getOrthoProjectionForSplit(i));
                this.world.draw(this.shadowsManager.getBoundaryCheckerForSplit(i));
            }
        }

        glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.tilesTexture);
        glBindFramebuffer(GL_FRAMEBUFFER, this.baseFbo);
        glClear(GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
        GL11.glViewport(0, 0, X_RES, Y_RES);
        this.world.overrideCurrentShader(null);
        this.world.overrideCurrentPVMatrix(null);
        this.world.draw(null); //Draws the world normally into the framebuffer basefbo, filling the color, normal, lighting and depth base textures

        this.liquidRenderer.renderLayers(this.world, X_RES, Y_RES); //Renders the liquid layers, one by one, into its respectives depth texutres

        glBindFramebuffer(GL_FRAMEBUFFER, this.baseFbo); //Binds the base fbo again for safeness (The liquid layers has binded their respective fbo each)

        nightDomeTexture.bind(); //Uses in GL_TEXTURE0 the night dome texture instead of the cubes textures from now on

        this.world.afterDrawTasks();

        glDisable(GL_DEPTH_TEST); //Depth test is no longer needed, we are drawing a texture
        glEnable(GL11.GL_CULL_FACE); //We only draw a quad looking to the screen, not its backface (Invisible)

        int[] fbos = { this.deferredFbo, 0 }; //Only two passes of deferred rendering remaining, so we use for the second a fbo and for the third we render to the screen

        DeferredShaderProgram[] programs = { this.DTSP, this.DRSP };
        if (this.world.isUnderwater()) { //Switching the shaders if we are underwater
            programs = new DeferredShaderProgram[] { this.DUTSP, this.DUFSP };
        }

        Matrix4f temp = Matrix4f.invert(this.cam.getProjectionViewMatrix(), null);
        this.finalDrawManager.draw(programs, fbos, temp, this.cam.getViewMatrix(), this.cam.getProjectionMatrix(),
                X_RES, Y_RES); //Draw the two remaining deferred passes

        this.HSP.enable();

        this.hud.draw(); //Draws the hud (Gray point on the center of the screen)
        this.HSP.disable();
        TextureImpl.bindNone();

        this.textManager.draw(X_RES, Y_RES); //Draws text, if needed
    }

    /**
     * Inits the game resources (Images, pools, shaders, objects, etc.)
     */
    private void initResources() throws IOException {
        //Inits static pools
        FloatBufferPool.init(Chunk.CHUNK_DIMENSION * Chunk.CHUNK_DIMENSION * Chunk.CHUNK_DIMENSION * 2 * 6 * 6, 20);
        ByteArrayPool.init(Chunk.CHUNK_DIMENSION,
                (settings.RENDER_DISTANCE * 2 + 1) * World.HEIGHT * (settings.RENDER_DISTANCE * 2 + 1) * 2);
        glEnable(GL11.GL_CULL_FACE);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL11.GL_BLEND);
        glClearColor(0.6f, 0.8f, 1.0f, 0f);

        //Inits shaders
        GL20.glUseProgram(0);
        textManager = new GlobalTextManager();
        this.DVSP = new DepthVoxelShaderProgram(true);
        this.VSP = new TerrainVoxelShaderProgram(true);
        this.HSP = new HudShaderProgram(true);
        this.DTSP = this.settings.SHADOWS_ENABLED ? new DeferredTerrainShaderProgram(true)
                : new DeferredTerrainUnshadowShaderProgram(true);
        this.DRSP = this.settings.REFLECTIONS_ENABLED ? new DeferredReflectionsShaderProgram(true)
                : new DeferredNoReflectionsShaderProgram(true);
        this.DUTSP = this.settings.SHADOWS_ENABLED ? new DeferredUnderwaterTerrainShaderProgram(true)
                : new DeferredUnderwaterUnshadowTerrainShaderProgram(true);
        this.DUFSP = new DeferredUnderwaterFinalShaderProgram(true);

        //Inits essential objects
        this.TM = new TimeManager();
        this.cam = new Camera(CAMERA_NEAR, CAMERA_FAR, 80f, (float) (X_RES * 3 / 4) / Y_RES); //FOV more width than height BY DESIGN, so blocks looks more "plane". Looks nicer that way, i think.
        this.camInvProjEnv = new CameraInverseProjEnvelope(this.cam);
        this.shadowsManager = new ShadowsManager(SHADOW_SPLITS, this.cam);
        this.liquidRenderer = new DepthPeelingLiquidRenderer(LIQUID_LAYERS);

        this.sunCam = new Camera(new Matrix4f());
        this.sunCam.moveTo(0, 5, 0);
        this.sunCam.setPitch(0);

        this.sky = new Sky(cam, this.sunCam);
        File mapRoute = new File(this.settings.MAP_ROUTE);
        mapRoute.mkdir(); //Creates the maps folder
        this.fileManager = new FileManager(mapRoute, settings.RENDER_DISTANCE);
        this.fileManager.getSettingsFromFile(settings); //Reads default settings from settings file.
        this.world = new World(this.VSP, this.cam, this.sunCam, this.shadowsManager, this.sky, fileManager,
                this.settings);
        this.finalDrawManager = new FinalDrawManager(this.world, this.sky, this.shadowsManager, this.liquidRenderer,
                this.camInvProjEnv.getInvProjMatrix(), CAMERA_NEAR, CAMERA_FAR);
        this.hud = new Hud(this.HSP, X_RES, Y_RES);

        //Load textures here
        glActiveTexture(TEXTURE_FETCH[TILES_TEXTURE_LOCATION]);

        //GL_NEAREST for that blocky look
        //loads the tiles textures into a array
        tilesTexture = Util.loadTextureAtlasIntoTextureArray(FileLoader.loadTileImages(), GL11.GL_NEAREST,
                GL11.GL_NEAREST_MIPMAP_LINEAR, true, settings.ANISOTROPIC_FILTERING_ENABLED);

        Util.loadPNGTexture(FileLoader.loadWaterNormalImage(), TEXTURE_FETCH[WATER_NORMAL_TEXTURE_LOCATION]); //loads the water normal texture

        glActiveTexture(GL_TEXTURE0);
        nightDomeTexture = TextureLoader.getTexture("JPG",
                ResourceLoader.getResourceAsStream("images/nightdome.jpg")); //loads the nightdome

        //Without this, text printing using Slick-Utils doesn't work. Seems it still uses some old mode OpenGL.
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glDisable(GL11.GL_DEPTH_TEST);
        GL11.glDisable(GL11.GL_LIGHTING);
        GL11.glClearDepth(1);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glViewport(0, 0, X_RES, Y_RES);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glLoadIdentity();
        GL11.glOrtho(0, X_RES, Y_RES, 0, 1, -1);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);

        //First pass deferred rendering
        this.baseFbo = glGenFramebuffers();
        glBindFramebuffer(GL_FRAMEBUFFER, this.baseFbo);

        int colorTexture = glGenTextures();
        int brightnessNormalsTexture = glGenTextures();

        //Creates and inits the base color texture as a RGB texture
        glActiveTexture(TEXTURE_FETCH[BASEFBO_COLOR_TEXTURE_LOCATION]);
        glBindTexture(GL_TEXTURE_2D, colorTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, X_RES, Y_RES, 0, GL_RGB, GL_UNSIGNED_BYTE, (FloatBuffer) null);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        GL30.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);

        //Creates and inits the brightness and normals texture as a RGBA texture
        glActiveTexture(TEXTURE_FETCH[BASEFBO_NORMALS_BRIGHTNESS_TEXTURE_LOCATION]);
        glBindTexture(GL_TEXTURE_2D, brightnessNormalsTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL11.GL_RGBA, X_RES, Y_RES, 0, GL11.GL_RGBA, GL_UNSIGNED_BYTE,
                (FloatBuffer) null);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        GL30.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, brightnessNormalsTexture,
                0);

        //The depth buffer of this FBO will be a texture, too. This will make depth sorting slower but we will be able to access depth values later.
        int baseFboDepth = glGenTextures();

        glActiveTexture(TEXTURE_FETCH[BASEFBO_DEPTH_TEXTURE_LOCATION]);
        glBindTexture(GL_TEXTURE_2D, baseFboDepth);
        glTexImage2D(GL_TEXTURE_2D, 0, GL14.GL_DEPTH_COMPONENT24, X_RES, Y_RES, 0, GL_DEPTH_COMPONENT,
                GL_UNSIGNED_INT, (FloatBuffer) null);
        System.out.println("ERR" + GL11.glGetError());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
        GL30.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, baseFboDepth, 0); //Set the depth texture as the default render depth target

        IntBuffer drawBuffers = BufferUtils.createIntBuffer(2); //Drawing to 2 textures

        drawBuffers.put(GL_COLOR_ATTACHMENT0);
        drawBuffers.put(GL_COLOR_ATTACHMENT1);

        drawBuffers.flip();
        GL20.glDrawBuffers(drawBuffers);

        //Second pass deferred rendering
        this.deferredFbo = glGenFramebuffers();
        glBindFramebuffer(GL_FRAMEBUFFER, this.deferredFbo);

        int deferredColorTex = glGenTextures();

        //Only uses one texture, a RGBA color texture
        glActiveTexture(TEXTURE_FETCH[DEFERREDFBO_COLOR_TEXTURE_LOCATION]);
        glBindTexture(GL_TEXTURE_2D, deferredColorTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL11.GL_RGBA, X_RES, Y_RES, 0, GL11.GL_RGBA, GL_UNSIGNED_BYTE,
                (FloatBuffer) null);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        GL30.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, deferredColorTex, 0);

        drawBuffers = BufferUtils.createIntBuffer(1);

        drawBuffers.put(GL_COLOR_ATTACHMENT0);

        drawBuffers.flip();
        GL20.glDrawBuffers(drawBuffers);

        //If shadows are enabled, we init each shadow map texture, placed in an array
        if (this.settings.SHADOWS_ENABLED) {

            int shadowTexture = glGenTextures();

            glActiveTexture(TEXTURE_FETCH[SHADOW_TEXTURE_LOCATION]);

            glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, shadowTexture);
            //Creates a texture array to place the shadows in
            GL12.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL14.GL_DEPTH_COMPONENT16, SHADOW_XRES, SHADOW_YRES,
                    this.shadowsManager.getNumberSplits(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT,
                    (FloatBuffer) null);
            System.out.println("ERR" + GL11.glGetError());
            glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); //Needed to do hardware PCF comparisons via shader
            glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); //Needed to do hardware PCF comparisons via shader
            glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL14.GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); //Needed to do hardware PCF comparisons via shader

            this.shadowFbos = new int[this.shadowsManager.getNumberSplits()];
            //Creates one framebuffer per shadow map
            for (int i = 0; i < this.shadowsManager.getNumberSplits(); i++) {
                this.shadowFbos[i] = glGenFramebuffers();
                glBindFramebuffer(GL_FRAMEBUFFER, this.shadowFbos[i]);
                GL30.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowTexture, 0, i); //Each framebuffer will have one texture layer (one index of the texture array created before) assigned as render target

                drawBuffers = BufferUtils.createIntBuffer(0);

                GL20.glDrawBuffers(drawBuffers);

            }
        }

        //Liquid layers depth generation. Equal to the shadows depth textures generation.
        int liquidLayers = glGenTextures();

        glActiveTexture(TEXTURE_FETCH[LIQUIDLAYERS_TEXTURE_LOCATION]);
        glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, liquidLayers);
        GL12.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL14.GL_DEPTH_COMPONENT24, X_RES, Y_RES,
                this.liquidRenderer.getNumLayers(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, (FloatBuffer) null);

        glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); //We will compare manually the depth in the shader, we will not perform PCF of any sort in this case
        glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);

        int currentLiquidNormalTex = glGenTextures();

        glActiveTexture(KubexGame.TEXTURE_FETCH[KubexGame.CURRENT_LIQUID_NORMAL_TEXTURE_LOCATION]);
        glBindTexture(GL_TEXTURE_2D, currentLiquidNormalTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL11.GL_RGB, X_RES, Y_RES, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE,
                (FloatBuffer) null);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

        this.liquidRenderer.initResources(liquidLayers, currentLiquidNormalTex);

        //Reset active texture and fbo to the default
        glActiveTexture(GL13.GL_TEXTURE0);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    /**
     * Handles keyboard inputs, settings the appropiate values on the InputHandler
     */
    private void handleKeyboardInput() {
        switch (Keyboard.getEventKey()) {
        case Keyboard.KEY_W:
            InputHandler.setW(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_A:
            InputHandler.setA(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_S:
            InputHandler.setS(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_D:
            InputHandler.setD(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_SPACE:
            InputHandler.setSPACE(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_LSHIFT:
            InputHandler.setSHIFT(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_ESCAPE:
            InputHandler.setESC(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_0:
            InputHandler.setNum(Keyboard.getEventKeyState(), 0);
            break;
        case Keyboard.KEY_1:
            InputHandler.setNum(Keyboard.getEventKeyState(), 1);
            break;
        case Keyboard.KEY_2:
            InputHandler.setNum(Keyboard.getEventKeyState(), 2);
            break;
        case Keyboard.KEY_3:
            InputHandler.setNum(Keyboard.getEventKeyState(), 3);
            break;
        case Keyboard.KEY_4:
            InputHandler.setNum(Keyboard.getEventKeyState(), 4);
            break;
        case Keyboard.KEY_5:
            InputHandler.setNum(Keyboard.getEventKeyState(), 5);
            break;
        case Keyboard.KEY_6:
            InputHandler.setNum(Keyboard.getEventKeyState(), 6);
            break;
        case Keyboard.KEY_7:
            InputHandler.setNum(Keyboard.getEventKeyState(), 7);
            break;
        case Keyboard.KEY_8:
            InputHandler.setNum(Keyboard.getEventKeyState(), 8);
            break;
        case Keyboard.KEY_9:
            InputHandler.setNum(Keyboard.getEventKeyState(), 9);
            break;
        case Keyboard.KEY_LCONTROL:
            InputHandler.setCtrl(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_E:
            InputHandler.setE(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_P:
            InputHandler.setP(Keyboard.getEventKeyState());
            break;
        case Keyboard.KEY_O:
            InputHandler.setO(Keyboard.getEventKeyState());
            break;
        }
    }

    /**
     * Handles mouse inputs, setting the appropiate values on the input handler
     */
    private void handleMouseInput() {
        switch (Mouse.getEventButton()) {
        case 0:
            InputHandler.setMouseButton1(Mouse.isButtonDown(0));
            break;
        case 1:
            InputHandler.setMouseButton2(Mouse.isButtonDown(1));
            break;
        }
    }

    /************************************************************************************* MAIN METHOD **********************************************************************************/

    public static void main(String args[]) throws LWJGLException, IOException {
        KubexSettings settings = new KubexSettings(); //Inits a default settings file

        File defaultConfigFile = new File("kubex_conf.txt"); //Opens default settings file (contains only menu preferences)
        loadDefaultConfigFile(settings, defaultConfigFile);

        File mapRoute = new File("kubex_maps"); //Opens the maps folder (If it doesn't exists, creates it)
        mapRoute.mkdir();

        //Opens the main menu
        MonecruftMenu menu = new MonecruftMenu(settings, mapRoute);

        //Gets blocked in the menu until notified
        if (menu.waitForClose()) {
            try {
                storeDefaultConfigFile(settings, defaultConfigFile); //Stores the default menu configs in the file.
                new KubexGame(settings); //Inits the game
            }
            //Error handling
            catch (KubexGPUException e) {
                JOptionPane.showMessageDialog(null,
                        "Your GPU can't run this game. Please update your drivers and try again\n\n"
                                + e.getMessage(),
                        "GPU Fatal error", JOptionPane.ERROR_MESSAGE);
            } catch (KubexIOException e) {
                JOptionPane.showMessageDialog(null,
                        "A disk reading/writing error has happened. Remember Kubex needs to write/read files in the same folder it's placed, so make sure you have admin rights in that folder.\n\n"
                                + e.getMessage(),
                        "Error reading data from disk", JOptionPane.ERROR_MESSAGE);
            } catch (Exception e) {
                JOptionPane
                        .showMessageDialog(null,
                                "Fatal exception thrown. Kubex will exit now.\nPlease send me this log :)\n\n"
                                        + exceptionStacktraceToString(e),
                                "Fatal Exception", JOptionPane.ERROR_MESSAGE);
            } catch (Error e) {
                JOptionPane.showMessageDialog(null, "A fatal error happened. Kubex will exit now.\n\n" + e,
                        "Fatal Error", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**
     * Inserts a exception stack into a string
     */
    private static String exceptionStacktraceToString(Exception e) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(baos);
        e.printStackTrace(ps);
        ps.close();
        return baos.toString();
    }

    /**
     * Loads default file <f> settings into the settings object <settings>
     */
    private static void loadDefaultConfigFile(KubexSettings settings, File f) {
        if (f.exists()) {
            try {
                Scanner s = new Scanner(f);
                while (s.hasNextLine()) {
                    String line = s.nextLine();
                    String[] content = line.split(":");
                    if (content[0].equals("MAP_SEED")) {
                        settings.MAP_SEED = Long.parseLong(content[1]);
                    } else if (content[0].equals("MAP_CODE")) {
                        settings.MAP_CODE = Integer.parseInt(content[1]);
                    } else if (content[0].equals("MAP_ROUTE")) {
                        settings.MAP_ROUTE = content[1];
                    } else if (content[0].equals("SHADOWS_ENABLED")) {
                        settings.SHADOWS_ENABLED = Boolean.parseBoolean(content[1]);
                    } else if (content[0].equals("REFLECTIONS_ENABLED")) {
                        settings.REFLECTIONS_ENABLED = Boolean.parseBoolean(content[1]);
                    } else if (content[0].equals("FULLSCREEN_ENABLED")) {
                        settings.FULLSCREEN_ENABLED = Boolean.parseBoolean(content[1]);
                    } else if (content[0].equals("WINDOW_XRES")) {
                        settings.WINDOW_XRES = Integer.parseInt(content[1]);
                    } else if (content[0].equals("WINDOW_YRES")) {
                        settings.WINDOW_YRES = Integer.parseInt(content[1]);
                    } else if (content[0].equals("RENDER_DISTANCE")) {
                        settings.RENDER_DISTANCE = Integer.parseInt(content[1]);
                    } else if (content[0].equals("WATER_LAYERS")) {
                        settings.WATER_LAYERS = Integer.parseInt(content[1]);
                    } else if (content[0].equals("ANISOTROPIC_FILTERING_ENABLED")) {
                        settings.ANISOTROPIC_FILTERING_ENABLED = Boolean.parseBoolean(content[1]);
                    } else if (content[0].equals("MOUSE_SENSITIVITY")) {
                        settings.MOUSE_SENSITIVITY = Float.parseFloat(content[1]);
                    }
                }
                s.close();
            } catch (FileNotFoundException e) {
                //Shouldn't happen, but no problem. Default config loaded.
            }
        }
    }

    /**
     * Stores all default settings in <settings> (Only menu settings> into the file <settingsFile>
     */
    private static void storeDefaultConfigFile(KubexSettings settings, File settingsFile) {
        try {
            if (!settingsFile.exists())
                settingsFile.createNewFile();
            PrintWriter f = new PrintWriter(settingsFile, "ISO-8859-1");
            f.println("MAP_SEED:" + settings.MAP_SEED);
            f.println("MAP_CODE:" + settings.MAP_CODE);
            f.println("MAP_ROUTE:" + settings.MAP_ROUTE);
            f.println("SHADOWS_ENABLED:" + settings.SHADOWS_ENABLED);
            f.println("REFLECTIONS_ENABLED:" + settings.REFLECTIONS_ENABLED);
            f.println("FULLSCREEN_ENABLED:" + settings.FULLSCREEN_ENABLED);
            f.println("WINDOW_XRES:" + settings.WINDOW_XRES);
            f.println("WINDOW_YRES:" + settings.WINDOW_YRES);
            f.println("RENDER_DISTANCE:" + settings.RENDER_DISTANCE);
            f.println("ANISOTROPIC_FILTERING_ENABLED:" + settings.ANISOTROPIC_FILTERING_ENABLED);
            f.println("WATER_LAYERS:" + settings.WATER_LAYERS);
            f.println("MOUSE_SENSITIVITY:" + settings.MOUSE_SENSITIVITY);
            f.close();
        }
        //If the file can't be created, there is some problem here. throws an exception
        catch (IOException e) {
            throw new KubexIOException("Error storing default config file in " + settingsFile.getAbsolutePath());
        }
    }

    /**
     * Safelly closes the app, closing all open resources. Applies both for closeApp() and fullClean(), a submethod of it.
     */
    private void closeApp() {
        Runtime.getRuntime().removeShutdownHook(shutdownHook);
        this.fullClean();
        // Dispose any resources and destroy our window
        Display.destroy();
    }

    @Override
    public void fullClean() {
        world.fullClean();
        this.fileManager.fullClean();
        VSP.fullClean();
        this.TM = null;
        this.cam = null;
    }
}