package electrosphere.renderer;

import electrosphere.engine.Globals;
import electrosphere.engine.assetmanager.AssetDataStrings;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;

import electrosphere.renderer.actor.ActorTextureMask;
import electrosphere.renderer.model.Material;
import electrosphere.renderer.model.Mesh;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.shader.VisualShader;
import electrosphere.renderer.texture.Texture;

import org.joml.Vector3f;
import org.lwjgl.BufferUtils;

import static org.lwjgl.opengl.GL11.GL_FLOAT;
import org.lwjgl.opengl.GL40;
import org.lwjgl.util.par.ParShapes;
import org.lwjgl.util.par.ParShapesMesh;

/**
 * Utilities to assist with rendering
 */
public class RenderUtils {

    /**
     * Name of the mesh for the single block model
     */
    public static final String MESH_NAME_BLOCK_SINGLE = "cube";
    
    
    
    static int createScreenTextureVAO(){
        int rVal = GL40.glGenVertexArrays();
        GL40.glBindVertexArray(rVal);
        //vertices
        FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(12);
        vertexArrayBufferData.put(-1.0f);
        vertexArrayBufferData.put( 1.0f);
        
        vertexArrayBufferData.put(-1.0f);
        vertexArrayBufferData.put(-1.0f);
        
        vertexArrayBufferData.put( 1.0f);
        vertexArrayBufferData.put(-1.0f);
        
        vertexArrayBufferData.put(-1.0f);
        vertexArrayBufferData.put( 1.0f);
        
        vertexArrayBufferData.put( 1.0f);
        vertexArrayBufferData.put(-1.0f);
        
        vertexArrayBufferData.put( 1.0f);
        vertexArrayBufferData.put( 1.0f);
        vertexArrayBufferData.flip();
        int vertexBuffer = GL40.glGenBuffers();
        GL40.glBindBuffer(GL40.GL_ARRAY_BUFFER, vertexBuffer);
        GL40.glBufferData(GL40.GL_ARRAY_BUFFER, vertexArrayBufferData, GL40.GL_STATIC_DRAW);
        GL40.glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
        GL40.glEnableVertexAttribArray(0);
        
        
        
        
        
        //texture coords
        FloatBuffer textureArrayBufferData = BufferUtils.createFloatBuffer(12);
        textureArrayBufferData.put(0.0f);
        textureArrayBufferData.put(1.0f);
        
        textureArrayBufferData.put(0.0f);
        textureArrayBufferData.put(0.0f);
        
        textureArrayBufferData.put(1.0f);
        textureArrayBufferData.put(0.0f);
        
        textureArrayBufferData.put(0.0f);
        textureArrayBufferData.put(1.0f);
        
        textureArrayBufferData.put(1.0f);
        textureArrayBufferData.put(0.0f);
        
        textureArrayBufferData.put(1.0f);
        textureArrayBufferData.put(1.0f);
        textureArrayBufferData.flip();
        int textureCoordBuffer = GL40.glGenBuffers();
        GL40.glBindBuffer(GL40.GL_ARRAY_BUFFER, textureCoordBuffer);
        GL40.glBufferData(GL40.GL_ARRAY_BUFFER, textureArrayBufferData, GL40.GL_STATIC_DRAW);
        GL40.glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
        GL40.glEnableVertexAttribArray(1);
        
        return rVal;
    }
    
    
    
    
    // public static void recaptureScreen(){
    //     //Makes the window that was just created the current OS-level window context
    //     // glfwMakeContextCurrent(Globals.window);
    //     //Maximize it
    //     // glfwMaximizeWindow(Globals.window);
    //     //grab focus
    //     GLFW.glfwFocusWindow(Globals.window);
    //     //apply mouse controls state
    //     if(Globals.controlHandler.isMouseVisible()){
    //         Globals.controlHandler.showMouse();
    //     } else {
    //         Globals.controlHandler.hideMouse();
    //     }
    // }
    
    
    
    
    
    
    
    
    
    public static Model createParticleModel(){
        Model particleModel = new Model();
        
        
        Mesh particleMesh = new Mesh("particleBillboard");
        
        
        //
        //  VAO
        //
        particleMesh.generateVAO();
        
        
        
        
        
        
        
        
        
        
        float[] vertexcoords = {
            -1.0f,  1.0f, 0.0f,
             1.0f,  1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
             1.0f, -1.0f, 0.0f,
        };
        
        //
        //Buffer data to GPU
        //
        
        try {
            int vertexCount = vertexcoords.length / 3;
            FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(vertexCount * 3);
            float[] temp = new float[3];
            for (int i = 0; i < vertexCount; i++) {
                temp[0] = vertexcoords[i * 3 + 0];
                temp[1] = vertexcoords[i * 3 + 1];
                temp[2] = vertexcoords[i * 3 + 2];
                vertexArrayBufferData.put(temp);
            }
            vertexArrayBufferData.flip();
            particleMesh.bufferVertices(vertexArrayBufferData, 3);
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        
        int[] facedata = {
            0,1,2,
            1,2,3,
        };
        
        //
        //  FACES
        //
        int faceCount = facedata.length / 3;
        int elementCount = facedata.length;
        IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(elementCount);
        for(int i = 0; i < faceCount; i++){
            int[] temp = new int[3];
            temp[0] = facedata[i * 3 + 0];
            temp[1] = facedata[i * 3 + 1];
            temp[2] = facedata[i * 3 + 2];
            elementArrayBufferData.put(temp);
        }
        elementArrayBufferData.flip();
        particleMesh.bufferFaces(elementArrayBufferData,elementCount);
        
        //
        //  TEXTURE COORDS
        //
        FloatBuffer texture_coords = BufferUtils.createFloatBuffer(8);
        float[] texturedata = {
            0,1,
            1,1,
            0,0,
            1,0
        };
        texture_coords.put(texturedata);
        texture_coords.flip();
        particleMesh.bufferTextureCoords(texture_coords, 2);
        
        
        
        
        particleMesh.setShader(VisualShader.smartAssembleOITProgram(false, true));
        
        
        
        
        GL40.glBindVertexArray(0);
        
        
        
        
        particleMesh.setMaterial(new Material(AssetDataStrings.TEXTURE_PARTICLE));
        
        
        
        
        particleMesh.setParent(particleModel);
        
        
        particleModel.getMeshes().add(particleMesh);
        
        
        return particleModel;
    }
    
    
    public static Model createPlaneModel(String vertexShader, String fragmentShader){
        Model rVal = new Model();
                
        
        Mesh planeMesh = new Mesh("plane");
        
        
        
        //
        //  VAO
        //
        planeMesh.generateVAO();
        
        
        
        
        
        
        
        
        
        
        float[] vertexcoords = {
            -1.0f,  1.0f, 0.0f,
             1.0f,  1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
             1.0f, -1.0f, 0.0f,
            
        };
        
        //
        //Buffer data to GPU
        //
        
        try {
            int vertexCount = vertexcoords.length / 3;
            FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(vertexCount * 3);
            float[] temp = new float[3];
            for (int i = 0; i < vertexCount; i++) {
                temp[0] = vertexcoords[i * 3 + 0];
                temp[1] = vertexcoords[i * 3 + 1];
                temp[2] = vertexcoords[i * 3 + 2];
                vertexArrayBufferData.put(temp);
            }
            vertexArrayBufferData.flip();
            planeMesh.bufferVertices(vertexArrayBufferData, 3);
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        
        int[] facedata = {
            0,1,2,
            1,2,3,
        };
        
        //
        //  FACES
        //
        int faceCount = facedata.length / 3;
        int elementCount = facedata.length;
        IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(elementCount);
        for(int i = 0; i < faceCount; i++){
            int[] temp = new int[3];
            temp[0] = facedata[i * 3 + 0];
            temp[1] = facedata[i * 3 + 1];
            temp[2] = facedata[i * 3 + 2];
            elementArrayBufferData.put(temp);
        }
        elementArrayBufferData.flip();
        planeMesh.bufferFaces(elementArrayBufferData,elementCount);
        
        //
        //  TEXTURE COORDS
        //
        FloatBuffer texture_coords = BufferUtils.createFloatBuffer(8);
        float[] texturedata = {
            0,1,
            1,1,
            0,0,
            1,0
        };
        texture_coords.put(texturedata);
        texture_coords.flip();
        planeMesh.bufferTextureCoords(texture_coords, 2);
        
        
        
        
        planeMesh.setShader(VisualShader.loadSpecificShader(vertexShader,fragmentShader));
        
        
        
        
        GL40.glBindVertexArray(0);
        
        
        
        
        
        
        
        
        
        planeMesh.setParent(rVal);
        
        
        rVal.getMeshes().add(planeMesh);
        
        
        return rVal;
    }

    /**
     * Generates a unit sphere model
     * @return The model
     */
    public static Model createUnitsphere(){
        Model model = new Model();
        Mesh sphereMesh = new Mesh("sphere");
        sphereMesh.generateVAO();
        
        //buffer coords
        ParShapesMesh data = ParShapes.par_shapes_create_parametric_sphere(10, 5);
        int numPoints = data.npoints();

        //verts
        {
            FloatBuffer verts = data.points(numPoints * 3);
            FloatBuffer vertsFinal = BufferUtils.createFloatBuffer(verts.limit()); //reallocating to BufferUtils buffer to help minimize memory errors
            vertsFinal.put(verts);
            vertsFinal.flip();
            sphereMesh.bufferVertices(vertsFinal, 3);
        }

        //indices
        {
            IntBuffer indices = data.triangles(data.ntriangles() * 3);
            IntBuffer indicesFinal = BufferUtils.createIntBuffer(indices.limit()); //reallocating to BufferUtils buffer to help minimize memory errors
            indicesFinal.put(indices);
            indicesFinal.flip();
            sphereMesh.bufferFaces(indicesFinal, data.ntriangles() * 3);
        }
        
        //texture coords
        {
            FloatBuffer texCoords = data.tcoords(numPoints * 3);
            FloatBuffer texCoordsFinal = BufferUtils.createFloatBuffer(texCoords.limit()); //reallocating to BufferUtils buffer to help minimize memory errors
            texCoordsFinal.put(texCoords);
            texCoordsFinal.flip();
            sphereMesh.bufferTextureCoords(texCoordsFinal, 2);
        }


        //setup extra structures
        Material mat = new Material();
        mat.set_diffuse(AssetDataStrings.TEXTURE_TEAL_TRANSPARENT);
        sphereMesh.setMaterial(mat);
        sphereMesh.setShader(VisualShader.smartAssembleShader(false, true));
        GL40.glBindVertexArray(0);
        sphereMesh.setParent(model);
        model.getMeshes().add(sphereMesh);

        return model;
    }

    /**
     * Creates a unit cylinder model
     * @return The model
     */
    public static Model createUnitCylinder(){
        Model model = new Model();
        Mesh sphereMesh = new Mesh("cylinder");
        sphereMesh.generateVAO();

        //buffer coords
        ParShapesMesh data = ParShapes.par_shapes_create_cylinder(10, 2);
        ParShapes.par_shapes_rotate(data, (float)(Math.PI / 2.0), new float[]{-1,0,0});
        ParShapes.par_shapes_translate(data, 0, -0.5f, 0);
        ParShapes.par_shapes_scale(data, -1.0f, 2.0f, 1.0f);
        int numPoints = data.npoints();

        //verts
        {
            FloatBuffer verts = data.points(numPoints * 3);
            FloatBuffer vertsFinal = BufferUtils.createFloatBuffer(verts.limit()); //reallocating to BufferUtils buffer to help minimize memory errors
            vertsFinal.put(verts);
            vertsFinal.flip();
            sphereMesh.bufferVertices(vertsFinal, 3);
        }

        //indices
        {
            IntBuffer indices = data.triangles(data.ntriangles() * 3);
            IntBuffer indicesFinal = BufferUtils.createIntBuffer(indices.limit()); //reallocating to BufferUtils buffer to help minimize memory errors
            indicesFinal.put(indices);
            indicesFinal.flip();
            sphereMesh.bufferFaces(indicesFinal, data.ntriangles() * 3);
        }
        
        //texture coords
        {
            FloatBuffer texCoords = data.tcoords(numPoints * 3);
            FloatBuffer texCoordsFinal = BufferUtils.createFloatBuffer(texCoords.limit()); //reallocating to BufferUtils buffer to help minimize memory errors
            texCoordsFinal.put(texCoords);
            texCoordsFinal.flip();
            sphereMesh.bufferTextureCoords(texCoordsFinal, 2);
        }

        //setup extra structures
        Material mat = new Material();
        mat.set_diffuse(AssetDataStrings.TEXTURE_TEAL_TRANSPARENT);
        sphereMesh.setMaterial(mat);
        sphereMesh.setShader(VisualShader.smartAssembleShader(false, true));
        GL40.glBindVertexArray(0);
        sphereMesh.setParent(model);
        model.getMeshes().add(sphereMesh);

        return model;
    }

    /**
     * Creates a unit cylinder model
     * @return The model
     */
    public static Model createUnitCube(){
        Model model = new Model();
        Mesh sphereMesh = new Mesh("cube");
        sphereMesh.generateVAO();

        //buffer coords
        int numTriangles = 12;

        //verts
        BufferUtils.createFloatBuffer(3 * 8);
        FloatBuffer verts = BufferUtils.createFloatBuffer(3 * 8);
        verts.put(new float[]{
            -0.5f,-0.5f,-0.5f,
             0.5f,-0.5f,-0.5f,
            -0.5f, 0.5f,-0.5f,
             0.5f, 0.5f,-0.5f,
            
            -0.5f,-0.5f, 0.5f,
             0.5f,-0.5f, 0.5f,
            -0.5f, 0.5f, 0.5f,
             0.5f, 0.5f, 0.5f,
        });
        verts.flip();
        sphereMesh.bufferVertices(verts, 3);

        //indices
        IntBuffer indices = BufferUtils.createIntBuffer(3*12);
        indices.put(new int[]{
            //Top
            2, 6, 7,
            2, 3, 7,

            //Bottom
            0, 4, 5,
            0, 1, 5,

            //Left
            0, 2, 6,
            0, 4, 6,

            //Right
            1, 3, 7,
            1, 5, 7,

            //Front
            0, 2, 3,
            0, 1, 3,

            //Back
            4, 6, 7,
            4, 5, 7
        });
        indices.flip();
        sphereMesh.bufferFaces(indices, numTriangles * 3);

        //texture coords
        FloatBuffer texCoords = BufferUtils.createFloatBuffer(2*8);
        texCoords.put(new float[]{
            0,0,
            1,0,
            0,1,
            1,1,

            0,0,
            0,1,
            1,0,
            1,1,
        });
        texCoords.flip();
        sphereMesh.bufferTextureCoords(texCoords, 2);

        //setup extra structures
        Material mat = new Material();
        mat.set_diffuse(AssetDataStrings.TEXTURE_TEAL_TRANSPARENT);
        sphereMesh.setMaterial(mat);
        sphereMesh.setShader(VisualShader.smartAssembleShader(false, true));
        GL40.glBindVertexArray(0);
        sphereMesh.setParent(model);
        model.getMeshes().add(sphereMesh);

        return model;
    }

    /**
     * Creates a unit cylinder model
     * @return The model
     */
    public static Model createBlockSingleModel(){
        Model model = new Model();
        Mesh cubeMesh = new Mesh(RenderUtils.MESH_NAME_BLOCK_SINGLE);
        cubeMesh.generateVAO();

        //buffer coords
        int numTriangles = 12;

        //verts
        BufferUtils.createFloatBuffer(3 * 8);
        FloatBuffer verts = BufferUtils.createFloatBuffer(3 * 8);
        verts.put(new float[]{
            -0.1f,-0.1f,-0.1f,
             0.1f,-0.1f,-0.1f,
            -0.1f, 0.1f,-0.1f,
             0.1f, 0.1f,-0.1f,
            
            -0.1f,-0.1f, 0.1f,
             0.1f,-0.1f, 0.1f,
            -0.1f, 0.1f, 0.1f,
             0.1f, 0.1f, 0.1f,
        });
        verts.flip();
        cubeMesh.bufferVertices(verts, 3);

        //indices
        IntBuffer indices = BufferUtils.createIntBuffer(3*12);
        indices.put(new int[]{
            //Top
            2, 6, 7,
            2, 3, 7,

            //Bottom
            0, 4, 5,
            0, 1, 5,

            //Left
            0, 2, 6,
            0, 4, 6,

            //Right
            1, 3, 7,
            1, 5, 7,

            //Front
            0, 2, 3,
            0, 1, 3,

            //Back
            4, 6, 7,
            4, 5, 7
        });
        indices.flip();
        cubeMesh.bufferFaces(indices, numTriangles * 3);

        //texture coords
        FloatBuffer texCoords = BufferUtils.createFloatBuffer(2*8);
        texCoords.put(new float[]{
            0,0,
            1,0,
            0,1,
            1,1,

            0,0,
            0,1,
            1,0,
            1,1,
        });
        texCoords.flip();
        cubeMesh.bufferTextureCoords(texCoords, 2);

        //setup extra structures
        Material mat = new Material();
        mat.set_diffuse(AssetDataStrings.TEXTURE_BLOCK_ATLAS);
        cubeMesh.setMaterial(mat);
        cubeMesh.setShader(VisualShader.loadSpecificShader(AssetDataStrings.SHADER_BLOCK_SINGLE_VERT, AssetDataStrings.SHADER_BLOCK_SINGLE_FRAG));
        GL40.glBindVertexArray(0);
        cubeMesh.setParent(model);
        model.getMeshes().add(cubeMesh);

        return model;
    }


    @Deprecated
    public static Model createBitmapDisplay(){
        
        Model rVal = new Model();
        Mesh m = new Mesh(AssetDataStrings.ASSET_STRING_BITMAP_FONT_MESH_NAME);
        m.generateVAO();
        //vertices
        FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(12);
        vertexArrayBufferData.put( 0);
        vertexArrayBufferData.put( 1);
        
        vertexArrayBufferData.put( 0);
        vertexArrayBufferData.put( 0);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put( 0);
        
        vertexArrayBufferData.put( 0);
        vertexArrayBufferData.put( 1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put( 0);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.flip();
        
        
        IntBuffer faceArrayBufferData = BufferUtils.createIntBuffer(6);
        faceArrayBufferData.put(0);
        faceArrayBufferData.put(1);
        faceArrayBufferData.put(2);
        
        faceArrayBufferData.put(3);
        faceArrayBufferData.put(4);
        faceArrayBufferData.put(5);
        faceArrayBufferData.flip();
        
        
        
        //texture coords
        FloatBuffer textureArrayBufferData = BufferUtils.createFloatBuffer(12);
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(1);
        
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(1);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(1);
        textureArrayBufferData.flip();
        
        
        //buffer vertices
        m.bufferVertices(vertexArrayBufferData, 2);
        //buffer normals
        m.bufferNormals(vertexArrayBufferData, 2);
        //buffer faces
        m.bufferFaces(faceArrayBufferData, 2);
        //buffer texture coords
        m.bufferTextureCoords(textureArrayBufferData, 2);
        
        
        m.setShader(VisualShader.loadSpecificShader("/Shaders/ui/font/basicbitmap/basicbitmap.vs", "/Shaders/ui/font/basicbitmap/basicbitmap.fs"));
        
        
        GL40.glBindVertexArray(0);
        m.setParent(rVal);
        
        Material uiMat = new Material();
        Globals.assetManager.addTexturePathtoQueue("/Textures/Fonts/myfont1-harsher.png");
        uiMat.set_diffuse("/Textures/Fonts/myfont1-harsher.png");
        uiMat.set_specular("/Textures/Fonts/myfont1-harsher.png");
        m.setMaterial(uiMat);
        rVal.getMaterials().add(uiMat);
        
        rVal.getMeshes().add(m);
        
        return rVal;
    }
    
    
    
    
    
    /**
     * Creates a model to use to show bitmap characters
     * @return The model
     */
    public static Model createBitmapCharacter(){
        
        Model rVal = new Model();
        Mesh m = new Mesh(AssetDataStrings.ASSET_STRING_BITMAP_FONT_MESH_NAME);
        m.generateVAO();
        //vertices
        FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(12);
        vertexArrayBufferData.put(-1);
        vertexArrayBufferData.put( 1);
        
        vertexArrayBufferData.put(-1);
        vertexArrayBufferData.put(-1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put(-1);
        
        vertexArrayBufferData.put(-1);
        vertexArrayBufferData.put( 1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put(-1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.flip();
        
        
        IntBuffer faceArrayBufferData = BufferUtils.createIntBuffer(6);
        faceArrayBufferData.put(0);
        faceArrayBufferData.put(1);
        faceArrayBufferData.put(2);
        
        faceArrayBufferData.put(3);
        faceArrayBufferData.put(4);
        faceArrayBufferData.put(5);
        faceArrayBufferData.flip();
        
        
        
        //texture coords
        FloatBuffer textureArrayBufferData = BufferUtils.createFloatBuffer(12);
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(1);
        
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(1);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(1);
        textureArrayBufferData.flip();
        
        
        //buffer vertices
        m.bufferVertices(vertexArrayBufferData, 2);
        //buffer normals
        m.bufferNormals(vertexArrayBufferData, 2);
        //buffer faces
        m.bufferFaces(faceArrayBufferData, 6);
        //buffer texture coords
        m.bufferTextureCoords(textureArrayBufferData, 2);
        
        
        m.setShader(VisualShader.loadSpecificShader("/Shaders/ui/font/bitmapchar/bitmapchar.vs", "/Shaders/ui/font/bitmapchar/bitmapchar.fs"));
        
        
        GL40.glBindVertexArray(0);
        m.setParent(rVal);
        
        rVal.getMeshes().add(m);
        
        return rVal;
    }



    public static Model createInWindowPanel(String vertexShader, String fragmentShader){
        
        Model rVal = new Model();
        Mesh m = new Mesh("plane");
        m.generateVAO();
        //vertices
        FloatBuffer vertexArrayBufferData = BufferUtils.createFloatBuffer(12);
        vertexArrayBufferData.put(-1);
        vertexArrayBufferData.put( 1);
        
        vertexArrayBufferData.put(-1);
        vertexArrayBufferData.put(-1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put(-1);
        
        vertexArrayBufferData.put(-1);
        vertexArrayBufferData.put( 1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put(-1);
        
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.put( 1);
        vertexArrayBufferData.flip();
        
        
        IntBuffer faceArrayBufferData = BufferUtils.createIntBuffer(6);
        faceArrayBufferData.put(0);
        faceArrayBufferData.put(1);
        faceArrayBufferData.put(2);
        
        faceArrayBufferData.put(3);
        faceArrayBufferData.put(4);
        faceArrayBufferData.put(5);
        faceArrayBufferData.flip();
        
        
        
        //texture coords
        FloatBuffer textureArrayBufferData = BufferUtils.createFloatBuffer(12);
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(1);
        
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(0);
        textureArrayBufferData.put(1);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(0);
        
        textureArrayBufferData.put(1);
        textureArrayBufferData.put(1);
        textureArrayBufferData.flip();
        
        
        //buffer vertices
        m.bufferVertices(vertexArrayBufferData, 2);
        //buffer normals
        m.bufferNormals(vertexArrayBufferData, 2);
        //buffer faces
        m.bufferFaces(faceArrayBufferData, 2);
        //buffer texture coords
        m.bufferTextureCoords(textureArrayBufferData, 2);
        
        
        m.setShader(VisualShader.loadSpecificShader(vertexShader, fragmentShader));
        
        
        GL40.glBindVertexArray(0);
        m.setParent(rVal);
        
        rVal.getMeshes().add(m);
        
        return rVal;
    }



    
    public static Model createTerrainModelPrecomputedShader(float[][] heightfield, float[][] texturemap, VisualShader program, int stride){
        Model rVal = new Model();
        Mesh m = new Mesh("terrain");
        int width = heightfield.length;
        int height = heightfield[0].length;
        
        int actualWidth = (int)Math.ceil(1.0f * width / (1.0f * stride));
        int actualHeight = (int)Math.ceil(1.0f * height / (1.0f * stride));
        
        
//        System.out.println(actualWidth + " " + actualHeight);
        
//        System.out.println((actualWidth - 1) * (actualHeight - 1));
        
        FloatBuffer vertices;
        FloatBuffer normals;
        IntBuffer faces;
        FloatBuffer texture_coords;
        FloatBuffer textureIndices;
        if(stride * actualWidth > width){
            int drawWidth = actualWidth + 1;
            int drawHeight = actualHeight + 1;
            vertices = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 12);
            normals = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 12);
            faces = BufferUtils.createIntBuffer((drawWidth - 1) * (drawHeight - 1) * 2 * 3);
            texture_coords = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 8);
            textureIndices = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 16);
        } else {
            vertices = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 12);
            normals = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 12);
            faces = BufferUtils.createIntBuffer((actualWidth - 1) * (actualHeight - 1) * 2 * 3);
            texture_coords = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 8);
            textureIndices = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 16);
        }
        
        int incrementer = 0;
        // int numFaces = (actualWidth - 1) * (actualHeight - 1) * 2 * 3;
        for(int x = 0; x < width - 1; x = x + stride){
            for(int y = 0; y < height - 1; y = y + stride){
                //deal with vertex
                //0,0
                vertices.put(x);
                vertices.put(heightfield[x][y]);
                vertices.put(y);
                //1,0
                vertices.put(x + stride);
                vertices.put(heightfield[x+stride][y]);
                vertices.put(y);
                //0,1
                vertices.put(x);
                vertices.put(heightfield[x][y+stride]);
                vertices.put(y + stride);
                //1,1
                vertices.put(x + stride);
                vertices.put(heightfield[x+stride][y+stride]);
                vertices.put(y + stride);
                //deal with normal
                Vector3f normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x, y);
                normals.put(normal.x);
                normals.put(normal.y);
                normals.put(normal.z);
                normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x + stride, y);
                normals.put(normal.x);
                normals.put(normal.y);
                normals.put(normal.z);
                normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x, y + stride);
                normals.put(normal.x);
                normals.put(normal.y);
                normals.put(normal.z);
                normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x + stride, y + stride);
                normals.put(normal.x);
                normals.put(normal.y);
                normals.put(normal.z);
                //deal with texture coordinates
//                if(x / stride % 2 == 0){
//                    if(y / stride % 2 == 0){
//                        texture_coords.put(0);
//                        texture_coords.put(0);
//                        texture_coords.put(1);
//                        texture_coords.put(0);
//                        texture_coords.put(0);
//                        texture_coords.put(1);
//                        texture_coords.put(1);
//                        texture_coords.put(1);
//                    } else {
//                        texture_coords.put(0);
//                        texture_coords.put(1);
//                    }
//                } else {
//                    if(y / stride % 2 == 0){
//                        texture_coords.put(1);
//                        texture_coords.put(0);
//                    } else {
//                        texture_coords.put(1);
//                        texture_coords.put(1);
//                    }
//                }
                texture_coords.put(0);
                texture_coords.put(0);
                texture_coords.put(1);
                texture_coords.put(0);
                texture_coords.put(0);
                texture_coords.put(1);
                texture_coords.put(1);
                texture_coords.put(1);
                
                if(x + stride < width - 1 && y + stride < height - 1){
//                    texturemap[x+stride][y+stride];
                    for(int i = 0; i < 4 ; i++){
//                        textureIndices.put(1);
//                        textureIndices.put(0);
//                        textureIndices.put(0);
//                        textureIndices.put(0);
                        textureIndices.put(texturemap[x][y]);
                        textureIndices.put(texturemap[x+stride][y]);
                        textureIndices.put(texturemap[x][y+stride]);
                        textureIndices.put(texturemap[x+stride][y+stride]);
                    }
                } else {
                    for(int i = 0; i < 4 ; i++){
                        textureIndices.put(0);
                        textureIndices.put(0);
                        textureIndices.put(0);
                        textureIndices.put(0);
                    }
                }
                
                
                //deal with faces
                if(1.0f * x / stride < actualWidth - 1 && 1.0f * y / stride < actualHeight - 1){
                    faces.put(incrementer * 4 + 0);
                    faces.put(incrementer * 4 + 1);
                    faces.put(incrementer * 4 + 2);
                    faces.put(incrementer * 4 + 1);
                    faces.put(incrementer * 4 + 2);
                    faces.put(incrementer * 4 + 3);
                }
                incrementer++;
            }
        }
        
        vertices.flip();
        normals.flip();
        faces.flip();
        texture_coords.flip();
        textureIndices.flip();
        
        m.generateVAO();
        //buffer vertices
        m.bufferVertices(vertices, 3);
        //buffer normals
        m.bufferNormals(normals, 3);
        //buffer faces
        m.bufferFaces(faces,incrementer*2);
        //buffer texture coords
        m.bufferTextureCoords(texture_coords, 2);
        //texture indices
        m.bufferCustomFloatAttribArray(textureIndices, 4, 5);
        m.setShader(program);
        GL40.glBindVertexArray(0);
        m.setParent(rVal);
        
        Material groundMat = new Material();
        Globals.assetManager.addTexturePathtoQueue("/Textures/Ground/Dirt1.png");
        groundMat.set_diffuse("/Textures/Ground/Dirt1.png");
        groundMat.set_specular("/Textures/Ground/Dirt1.png");
        m.setMaterial(groundMat);
        
        rVal.getMeshes().add(m);
        return rVal;
    }
    
    
    static float MINIMIZATION_DIFF_MAX = 0.001f;
    
    public static Model createMinimizedTerrainModelPrecomputedShader(float[][] heightfield, float[][] texturemap, VisualShader program, int stride){
        
        class QuadToGenerate {
            //coords are inclusive
            int startX;
            int endX;
            int startY;
            int endY;
            float min;
            float max;
            float texture;
            boolean homogeneousTexture;
            
            QuadToGenerate(int startX, int startY, int endX, int endY, float diff, float min, float max, boolean homogeneousTexture, float texture){
                this.startX = startX;
                this.startY = startY;
                this.endX = endX;
                this.endY = endY;
                this.min = min;
                this.max = max;
                this.texture = texture;
                this.homogeneousTexture = homogeneousTexture;
            }
            
        }
        
        
        Model rVal = new Model();
        Mesh m = new Mesh("terrain");
        int width = heightfield.length;
        int height = heightfield[0].length;
        
        int actualWidth = (int)Math.ceil(1.0f * width / (1.0f * stride));
        int actualHeight = (int)Math.ceil(1.0f * height / (1.0f * stride));
        
        
//        System.out.println(actualWidth + " " + actualHeight);
        
//        System.out.println((actualWidth - 1) * (actualHeight - 1));
        
        FloatBuffer vertices;
        FloatBuffer normals;
        IntBuffer faces;
        FloatBuffer texture_coords;
        FloatBuffer textureIndices;
        if(stride * actualWidth > width){
            int drawWidth = actualWidth + 1;
            int drawHeight = actualHeight + 1;
            vertices = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 12);
            normals = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 12);
            faces = BufferUtils.createIntBuffer((drawWidth - 1) * (drawHeight - 1) * 2 * 3);
            texture_coords = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 8);
            textureIndices = BufferUtils.createFloatBuffer(drawWidth * drawHeight * 16);
        } else {
            vertices = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 12);
            normals = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 12);
            faces = BufferUtils.createIntBuffer((actualWidth - 1) * (actualHeight - 1) * 2 * 3);
            texture_coords = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 8);
            textureIndices = BufferUtils.createFloatBuffer(actualWidth * actualHeight * 16);
        }
        
        
        //merge along y
        
        List<QuadToGenerate> firstPhaseQuads = new LinkedList<QuadToGenerate>();
        QuadToGenerate quadCurrent = null;
        float minVal = 0;
        float maxVal = 0;
        for(int x = 0; x < width - 1; x = x + stride){
            quadCurrent = null;
            for(int y = 0; y < height - 1; y = y + stride){
                if((x == 5 && y == 2)){
                    // System.out.println(quadCurrent);
//                    continue;
                }
                if(quadCurrent == null){
                    minVal = 100000000;
                    maxVal = 0;
                    //minval
                    if(heightfield[x][y] < minVal){
                        minVal = heightfield[x][y];
                    }
                    if(heightfield[x+stride][y] < minVal){
                        minVal = heightfield[x+stride][y];
                    }
                    if(heightfield[x][y+stride] < minVal){
                        minVal = heightfield[x][y+stride];
                    }
                    if(heightfield[x+stride][y+stride] < minVal){
                        minVal = heightfield[x+stride][y+stride];
                    }
                    //maxval
                    if(heightfield[x][y] > maxVal){
                        maxVal = heightfield[x][y];
                    }
                    if(heightfield[x+stride][y] > maxVal){
                        maxVal = heightfield[x+stride][y];
                    }
                    if(heightfield[x][y+stride] > maxVal){
                        maxVal = heightfield[x][y+stride];
                    }
                    if(heightfield[x+stride][y+stride] > maxVal){
                        maxVal = heightfield[x+stride][y+stride];
                    }
                    boolean textureMatch = false;
                    float texture = -1;
                    if(x+stride < width - 1 && y+stride < height - 1 && 
                            texturemap[x][y] == texturemap[x+stride][y] &&
                            texturemap[x][y] == texturemap[x][y+stride] &&
                            texturemap[x][y] == texturemap[x+stride][y+stride]){
                        
                        textureMatch = true;
                        texture = texturemap[x][y];
                    } else {
//                        if(x > 8 && (x+stride < width - 1) && (y+stride < height -1)){
//                            System.out.println(
//                                    (x+stride < width - 1) + " " +
//                                    (y+stride < height -1) + " " +
//                                    (texturemap[x][y] == texturemap[x+stride][y]) + " " +
//                            (texturemap[x][y] == texturemap[x][y+stride]) + " " +
//                            (texturemap[x][y] == texturemap[x+stride][y+stride])
//                            );
//                        }
                    }
                    if(textureMatch){
                        quadCurrent = new QuadToGenerate(x,y,x+stride,y+stride,maxVal - minVal,minVal,maxVal,textureMatch,texture);
                    } else {
                        firstPhaseQuads.add(new QuadToGenerate(x,y,x+stride,y+stride,maxVal - minVal,minVal,maxVal,textureMatch,texture));
                        // quadCurrent = null;
                    }
                } else {
                    float newMin = minVal;
                    float newMax = maxVal;
                    //min
                    if(heightfield[x][y+stride] < newMin){
                        newMin = heightfield[x][y+stride];
                    }
                    if(heightfield[x+stride][y+stride] < newMin){
                        newMin = heightfield[x+stride][y+stride];
                    }
                    //max
                    if(heightfield[x][y+stride] > newMax){
                        newMax = heightfield[x][y+stride];
                    }
                    if(heightfield[x+stride][y+stride] > newMax){
                        newMax = heightfield[x+stride][y+stride];
                    }
                    if(y+stride < height - 1 && x+stride < width - 1){
                        if(newMax - newMin < MINIMIZATION_DIFF_MAX &&
                            quadCurrent.texture == texturemap[x+stride][y] &&
                            quadCurrent.texture == texturemap[x       ][y+stride] &&
                            quadCurrent.texture == texturemap[x+stride][y+stride] &&
                            quadCurrent.homogeneousTexture
                                ){
                            //add to quad
                            quadCurrent.endY = y + stride;
                            quadCurrent.min = newMax;
                            quadCurrent.max = newMax;
                        } else {
                            //push quad
                            firstPhaseQuads.add(quadCurrent);
                            firstPhaseQuads.add(new QuadToGenerate(x,y,x+stride,y+stride,maxVal - minVal,minVal,maxVal,false,0));
                            quadCurrent = null;
//                            System.out.println("Push");
                        }
                    } else {
                        if(newMax - newMin < MINIMIZATION_DIFF_MAX){
                            //add to quad
                            quadCurrent.endY = y + stride;
                            quadCurrent.min = newMax;
                            quadCurrent.max = newMax;
                        } else {
                            //push quad that we were building
                            firstPhaseQuads.add(quadCurrent);
                            // quadCurrent = null;
                            //create new quad from what we were just analyzing
                            boolean textureMatch = false;
                            float texture = -1;
                            quadCurrent = new QuadToGenerate(x,y,x+stride,y+stride,maxVal - minVal,minVal,maxVal,textureMatch,texture);
//                            System.out.println("Push");
                        }
                    }
                }
            }
            if(quadCurrent != null){
                firstPhaseQuads.add(quadCurrent);
            }
        }
        
        List<QuadToGenerate> finalQuads = new LinkedList<QuadToGenerate>();
        // for(QuadToGenerate current : firstPhaseQuads){
        //     finalQuads.add(current);
        // }
        
//        System.out.println(finalQuads.size());
        
        //merge along x
        
    //    QuadToGenerate currentQuad = null;
        List<QuadToGenerate> toSkip = new LinkedList<QuadToGenerate>();
       for(QuadToGenerate currentQuad : firstPhaseQuads){
        // toRemove.clear();
            if(toSkip.contains(currentQuad)){
                continue;
            }
            for(QuadToGenerate currentPotentialMatch : firstPhaseQuads){
               if(currentPotentialMatch.startX <= currentQuad.startX){
                   continue;
               }
               if(currentPotentialMatch.startX > currentQuad.endX){
                   break;
               }
               if(currentPotentialMatch.startY != currentQuad.startY){
                   continue;
               }
               if(currentPotentialMatch.endY != currentQuad.endY){
                   continue;
               }
               if(
                   !(currentQuad.homogeneousTexture &&
                   currentPotentialMatch.homogeneousTexture &&
                   currentQuad.texture == currentPotentialMatch.texture)
                   ){
                    continue;
                }
                if(currentQuad.min < currentPotentialMatch.min && currentQuad.max < currentPotentialMatch.max){
                    float min = currentQuad.min;
                    float max = currentPotentialMatch.max;
                    if(max - min < MINIMIZATION_DIFF_MAX){
                        currentQuad.endX = currentPotentialMatch.endX;
                        currentQuad.max = currentPotentialMatch.max;
                        toSkip.add(currentPotentialMatch);
                    }
                } else if(currentQuad.min > currentPotentialMatch.min && currentQuad.max > currentPotentialMatch.max){
                    float min = currentPotentialMatch.min;
                    float max = currentQuad.max;
                    if(max - min < MINIMIZATION_DIFF_MAX){
                        currentQuad.endX = currentPotentialMatch.endX;
                        currentQuad.min = currentPotentialMatch.min;
                        toSkip.add(currentPotentialMatch);
                    }
                } else {
                    if(currentQuad.min < currentPotentialMatch.min){
                        currentQuad.endX = currentPotentialMatch.endX;
                    } else {
                        currentQuad.endX = currentPotentialMatch.endX;
                        currentQuad.min = currentPotentialMatch.min;
                        currentQuad.max = currentPotentialMatch.max;
                    }
                    toSkip.add(currentPotentialMatch);
                }
            }
           finalQuads.add(currentQuad);
       }
    //    for(QuadToGenerate currentIteration : firstPhaseQuads){
    //        if(currentQuad == null){
    //            currentQuad = currentIteration;
    //        } else {
    //            //if should merge:
    //            if(
    //                currentQuad.homogeneousTexture &&
    //                currentIteration.homogeneousTexture &&
    //                currentQuad.texture == currentIteration.texture
    //                ){
    //                 if(currentQuad.min < currentIteration.min && currentQuad.max < currentIteration.max){
    //                     float min = currentQuad.min;
    //                     float max = currentIteration.max;
    //                     if(max - min < MINIMIZATION_DIFF_MAX){
    //                         currentQuad.endX = currentIteration.endX;
    //                         currentQuad.max = currentIteration.max;
    //                     } else {
    //                         finalQuads.add(currentQuad);
    //                         currentQuad = currentIteration;
    //                     }
    //                 } else if(currentQuad.min > currentIteration.min && currentQuad.max > currentIteration.max){
    //                     float min = currentIteration.min;
    //                     float max = currentQuad.max;
    //                     if(max - min < MINIMIZATION_DIFF_MAX){
    //                         currentQuad.endX = currentIteration.endX;
    //                         currentQuad.min = currentIteration.min;
    //                     } else {
    //                         finalQuads.add(currentQuad);
    //                         currentQuad = currentIteration;
    //                     }
    //                 } else {
    //                     if(currentQuad.min < currentIteration.min){
    //                         currentQuad.endX = currentIteration.endX;
    //                     } else {
    //                         currentQuad.endX = currentIteration.endX;
    //                         currentQuad.min = currentIteration.min;
    //                         currentQuad.max = currentIteration.max;
    //                     }
    //                 }
    //             } else {
    //                 finalQuads.add(currentQuad);
    //                 currentQuad = currentIteration;
    //             }
    //        }
    //    }
    //    finalQuads.add(currentQuad);
        
        // for(QuadToGenerate current : finalQuads){
        //     if(current.startX > 0 && current.startY > 0 && current.endX < 99 && current.endY < 99){
        //         System.out.println(current.startX + " " + current.startY + " " + current.endX + " " + current.endY);
        //     }
        // }

        // System.out.println("AAAAAAAAAAAAAAAAAA");
//        System.out.println(finalQuads.size());
        
        int incrementer = 0;
        
        for(QuadToGenerate current : finalQuads){
            //deal with vertex
            //0,0
            vertices.put(current.startX);
            vertices.put(heightfield[current.startX][current.startY]);
            vertices.put(current.startY);
            //1,0
            vertices.put(current.endX);
            vertices.put(heightfield[current.endX][current.startY]);
            vertices.put(current.startY);
            //0,1
            vertices.put(current.startX);
            vertices.put(heightfield[current.startX][current.endY]);
            vertices.put(current.endY);
            //1,1
            vertices.put(current.endX);
            vertices.put(heightfield[current.endX][current.endY]);
            vertices.put(current.endY);
            //deal with normal
            Vector3f normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, current.startX, current.startY);
            normals.put(normal.x);
            normals.put(normal.y);
            normals.put(normal.z);
            normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, current.endX, current.startY);
            normals.put(normal.x);
            normals.put(normal.y);
            normals.put(normal.z);
            normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, current.startX, current.endY);
            normals.put(normal.x);
            normals.put(normal.y);
            normals.put(normal.z);
            normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, current.endX, current.endY);
            normals.put(normal.x);
            normals.put(normal.y);
            normals.put(normal.z);
            //deal with texture coordinates
//                if(x / stride % 2 == 0){
//                    if(y / stride % 2 == 0){
//                        texture_coords.put(0);
//                        texture_coords.put(0);
//                        texture_coords.put(1);
//                        texture_coords.put(0);
//                        texture_coords.put(0);
//                        texture_coords.put(1);
//                        texture_coords.put(1);
//                        texture_coords.put(1);
//                    } else {
//                        texture_coords.put(0);
//                        texture_coords.put(1);
//                    }
//                } else {
//                    if(y / stride % 2 == 0){
//                        texture_coords.put(1);
//                        texture_coords.put(0);
//                    } else {
//                        texture_coords.put(1);
//                        texture_coords.put(1);
//                    }
//                }
            texture_coords.put(0);
            texture_coords.put(0);
            texture_coords.put(current.endX - current.startX);
            texture_coords.put(0);
            texture_coords.put(0);
            texture_coords.put(current.endY - current.startY);
            texture_coords.put(current.endX - current.startX);
            texture_coords.put(current.endY - current.startY);

            if(current.endX < width - 1 && current.endY < height - 1){
//                    texturemap[x+stride][y+stride];
                for(int i = 0; i < 4 ; i++){
//                        textureIndices.put(1);
//                        textureIndices.put(0);
//                        textureIndices.put(0);
//                        textureIndices.put(0);
                    textureIndices.put(texturemap[current.startX][current.startY]);
                    textureIndices.put(texturemap[current.endX][current.startY]);
                    textureIndices.put(texturemap[current.startX][current.endY]);
                    textureIndices.put(texturemap[current.endX][current.endY]);
                }
            } else {
                for(int i = 0; i < 4 ; i++){
                    textureIndices.put(0);
                    textureIndices.put(0);
                    textureIndices.put(0);
                    textureIndices.put(0);
                }
            }


            //deal with faces
            faces.put(incrementer * 4 + 0);
            faces.put(incrementer * 4 + 1);
            faces.put(incrementer * 4 + 2);
            faces.put(incrementer * 4 + 1);
            faces.put(incrementer * 4 + 2);
            faces.put(incrementer * 4 + 3);
            
            incrementer++;
        }
        
        
        
//        int numFaces = (actualWidth - 1) * (actualHeight - 1) * 2 * 3;
//        for(int x = 0; x < width - 1; x = x + stride){
//            for(int y = 0; y < height - 1; y = y + stride){
//                //deal with vertex
//                //0,0
//                vertices.put(x);
//                vertices.put(heightfield[x][y]);
//                vertices.put(y);
//                //1,0
//                vertices.put(x + stride);
//                vertices.put(heightfield[x+stride][y]);
//                vertices.put(y);
//                //0,1
//                vertices.put(x);
//                vertices.put(heightfield[x][y+stride]);
//                vertices.put(y + stride);
//                //1,1
//                vertices.put(x + stride);
//                vertices.put(heightfield[x+stride][y+stride]);
//                vertices.put(y + stride);
//                //deal with normal
//                Vector3f normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x, y);
//                normals.put(normal.x);
//                normals.put(normal.y);
//                normals.put(normal.z);
//                normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x + stride, y);
//                normals.put(normal.x);
//                normals.put(normal.y);
//                normals.put(normal.z);
//                normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x, y + stride);
//                normals.put(normal.x);
//                normals.put(normal.y);
//                normals.put(normal.z);
//                normal = calculateTerrainNormal(heightfield, actualWidth, actualHeight, stride, x + stride, y + stride);
//                normals.put(normal.x);
//                normals.put(normal.y);
//                normals.put(normal.z);
//                //deal with texture coordinates
////                if(x / stride % 2 == 0){
////                    if(y / stride % 2 == 0){
////                        texture_coords.put(0);
////                        texture_coords.put(0);
////                        texture_coords.put(1);
////                        texture_coords.put(0);
////                        texture_coords.put(0);
////                        texture_coords.put(1);
////                        texture_coords.put(1);
////                        texture_coords.put(1);
////                    } else {
////                        texture_coords.put(0);
////                        texture_coords.put(1);
////                    }
////                } else {
////                    if(y / stride % 2 == 0){
////                        texture_coords.put(1);
////                        texture_coords.put(0);
////                    } else {
////                        texture_coords.put(1);
////                        texture_coords.put(1);
////                    }
////                }
//                texture_coords.put(0);
//                texture_coords.put(0);
//                texture_coords.put(1);
//                texture_coords.put(0);
//                texture_coords.put(0);
//                texture_coords.put(1);
//                texture_coords.put(1);
//                texture_coords.put(1);
//                
//                if(x + stride < width - 1 && y + stride < height - 1){
////                    texturemap[x+stride][y+stride];
//                    for(int i = 0; i < 4 ; i++){
////                        textureIndices.put(1);
////                        textureIndices.put(0);
////                        textureIndices.put(0);
////                        textureIndices.put(0);
//                        textureIndices.put(texturemap[x][y]);
//                        textureIndices.put(texturemap[x+stride][y]);
//                        textureIndices.put(texturemap[x][y+stride]);
//                        textureIndices.put(texturemap[x+stride][y+stride]);
//                    }
//                } else {
//                    for(int i = 0; i < 4 ; i++){
//                        textureIndices.put(0);
//                        textureIndices.put(0);
//                        textureIndices.put(0);
//                        textureIndices.put(0);
//                    }
//                }
//                
//                
//                //deal with faces
//                if(1.0f * x / stride < actualWidth - 1 && 1.0f * y / stride < actualHeight - 1){
//                    faces.put(incrementer * 4 + 0);
//                    faces.put(incrementer * 4 + 1);
//                    faces.put(incrementer * 4 + 2);
//                    faces.put(incrementer * 4 + 1);
//                    faces.put(incrementer * 4 + 2);
//                    faces.put(incrementer * 4 + 3);
//                }
//                incrementer++;
//            }
//        }

        // System.out.println(incrementer + " quads");
        
        vertices.flip();
        normals.flip();
        faces.flip();
        texture_coords.flip();
        textureIndices.flip();
        
        m.generateVAO();
        //buffer vertices
        m.bufferVertices(vertices, 3);
        //buffer normals
        m.bufferNormals(normals, 3);
        //buffer faces
        m.bufferFaces(faces,incrementer*2);
        //buffer texture coords
        m.bufferTextureCoords(texture_coords, 2);
        //texture indices
        m.bufferCustomFloatAttribArray(textureIndices, 4, 5);
        m.setShader(program);
        GL40.glBindVertexArray(0);
        m.setParent(rVal);
        
        Material groundMat = new Material();
        Globals.assetManager.addTexturePathtoQueue("/Textures/Ground/Dirt1.png");
        groundMat.set_diffuse("/Textures/Ground/Dirt1.png");
        groundMat.set_specular("/Textures/Ground/Dirt1.png");
        m.setMaterial(groundMat);
        
        rVal.getMeshes().add(m);
        return rVal;
    }
    
    static Vector3f calculateTerrainNormal(float[][] heightfield, int actualWidth, int actualHeight, int stride, int x, int y){
        Vector3f rVal = new Vector3f();
        if(x / stride < actualWidth - 1){
            if(y / stride < actualHeight - 1){
                float hL;
                if(x > 0){
                    hL = heightfield[x-1][y];
                } else {
                    hL = heightfield[x][y];
                }
                float hR = heightfield[x+1][y];
                float hD = heightfield[x][y+1];
                float hU;
                if(y > 0){
                    hU = heightfield[x][y-1];
                } else {
                    hU = heightfield[x][y];
                }
                rVal = new Vector3f(hL - hR, 2.0f, hD - hU);
                rVal.normalize();
            } else {
                float hL;
                if(x > 0){
                    hL = heightfield[x-1][y];
                } else {
                    hL = heightfield[x][y];
                }
                float hR = heightfield[x+1][y];
                float hD = heightfield[x][y];
                float hU = heightfield[x][y-1];
                rVal = new Vector3f(hL - hR, 2.0f, hD - hU);
                rVal.normalize();
            }
        } else {
            if(y / stride < actualHeight - 1){
                float hL = heightfield[x-1][y];
                float hR = heightfield[x][y];
                float hD = heightfield[x][y+1];
                float hU;
                if(y > 0){
                    hU = heightfield[x][y-1];
                } else {
                    hU = heightfield[x][y];
                }
                rVal = new Vector3f(hL - hR, 2.0f, hD - hU);
                rVal.normalize();
            } else {
                float hL = heightfield[x-1][y];
                float hR = heightfield[x][y];
                float hD = heightfield[x][y];
                float hU = heightfield[x][y-1];
                rVal = new Vector3f(hL - hR, 2.0f, hD - hU);
                rVal.normalize();
            }
        }
        return rVal;
    }

    public static ActorTextureMask generateVolumetricTextureMask(String meshName){
        List<Texture> textureList = new LinkedList<Texture>();
        textureList.add(Globals.renderingEngine.getVolumeFrontfaceTexture());
        textureList.add(Globals.renderingEngine.getVolumeBackfaceTexture());
        List<String> uniformList = new LinkedList<String>();
        uniformList.add("volumeDepthFrontface");
        uniformList.add("volumeDepthBackface");
        return new ActorTextureMask(meshName,textureList,uniformList);
    }

    

    
}
