wrath.client.graphics.Model.java Source code

Java tutorial

Introduction

Here is the source code for wrath.client.graphics.Model.java

Source

/**
 *  Wrath Engine 
 *  Copyright (C) 2015  Trent Spears
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package wrath.client.graphics;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.util.vector.Vector3f;
import wrath.client.Game;
import wrath.common.Closeable;
import wrath.common.Reloadable;
import wrath.util.Logger;

/**
 * Class to represent a Model (both 2D and 3D).
 * @author Trent Spears
 */
public class Model implements Renderable, Closeable, Reloadable {
    private static final int NORMALS_ATTRIB_INDEX = 2;
    private static final int TEXTURE_ATTRIB_INDEX = 1;
    private static final int VERTICIES_ATTRIB_INDEX = 0;

    /**
     * Creates a 2D or 3D model from a list of verticies.
     * Models are always assumed to be made with triangles, and will be rendered as such.
     * @param name The {@link java.lang.String} name of the Model.
     * @param verticies The list of verticies in the model. One point is represented by (x, y, z), and there must be at least 3 points.
     * @param indicies The list of points to connect for OpenGL. Look up indicies in OpenGL for reference.
     * @param normals The list of 3 float vectors describing the normal vector of the model's surface.
     * @return Returns the {@link wrath.client.graphics.Model} object of your model.
     */
    public static Model createModel(String name, float[] verticies, int[] indicies, float[] normals) {
        return createModel(name, verticies, indicies, normals, true);
    }

    /**
     * Creates a 2D or 3D model from a list of verticies.
     * Models are always assumed to be made with triangles, and will be rendered as such.
     * @param name The {@link java.lang.String} name of the Model.
     * @param verticies The list of verticies in the model. One point is represented by (x, y, z), and there must be at least 3 points.
     * @param indicies The list of points to connect for OpenGL. Look up indicies in OpenGL for reference.
     * @param normals The list of 3 float vectors describing the normal vector of the model's surface.
     * @param useDefaultShaders If true, shaders will be set up automatically.
     * @return Returns the {@link wrath.client.graphics.Model} object of your model.
     */
    public static Model createModel(String name, float[] verticies, int[] indicies, float[] normals,
            boolean useDefaultShaders) {
        // Generating VAO
        int vaoid = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vaoid);

        // Generating Verticies VBO
        int vtvboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vtvboid);
        FloatBuffer vbuffer = BufferUtils.createFloatBuffer(verticies.length);
        vbuffer.put(verticies);
        vbuffer.flip();
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vbuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(VERTICIES_ATTRIB_INDEX, 3, GL11.GL_FLOAT, false, 0, 0);

        // Generating Normals VBO
        int nmvboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, nmvboid);
        FloatBuffer nbuffer = BufferUtils.createFloatBuffer(normals.length);
        nbuffer.put(normals);
        nbuffer.flip();
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, nbuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(NORMALS_ATTRIB_INDEX, 3, GL11.GL_FLOAT, false, 0, 0);

        // Generating Indicies VBO
        int invboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, invboid);
        IntBuffer ibuffer = BufferUtils.createIntBuffer(indicies.length);
        ibuffer.put(indicies);
        ibuffer.flip();
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ibuffer, GL15.GL_STATIC_DRAW);

        // Creating Model Object
        Model model;
        File mfile = new File("assets/models/" + name);
        if (!mfile.exists())
            model = new Model(name, vaoid, new Integer[] { vtvboid, invboid, nmvboid }, verticies, indicies,
                    normals, useDefaultShaders);
        else
            model = new Model(name, vaoid, new Integer[] { vtvboid, invboid, nmvboid }, null, null, null,
                    useDefaultShaders);

        Game.getCurrentInstance().getLogger().println("Loaded model '" + name + "' with " + verticies.length
                + " verticies, " + indicies.length + " indicies, and " + normals.length + " normals.");
        if (useDefaultShaders)
            model.attachShader(ShaderProgram.DEFAULT_SHADER);

        // Unbinding OpenGL Objects
        GL30.glBindVertexArray(0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        Game.getCurrentInstance().addToTrashCleanup(model);
        Game.getCurrentInstance().addToRefreshList(model);
        return model;
    }

    /**
     * Loads a 2D or 3D model from specified name.
     * Models are always assumed to be made with triangles, and will be rendered as such.
     * @param modelName The name of the model to be loaded. This should be the entire file name, e.g. 'model.obj.
     * @return Returns the {@link wrath.client.graphics.Model} object of your model.
     */
    public static Model loadModel(String modelName) {
        return loadModel(new File("assets/models/" + modelName), true);
    }

    /**
     * Loads a 2D or 3D model from specified name.
     * Models are always assumed to be made with triangles, and will be rendered as such.
     * @param modelName The name of the model to be loaded. This should be the entire file name, e.g. 'model.obj.
     * @param useDefaultShaders If true, shaders will be set up automatically.
     * @return Returns the {@link wrath.client.graphics.Model} object of your model.
     */
    public static Model loadModel(String modelName, boolean useDefaultShaders) {
        return loadModel(new File("assets/models/" + modelName), useDefaultShaders);
    }

    /**
     * Loads a 2D or 3D model from specified {@link java.io.File}.
     * Models are always assumed to be made with triangles, and will be rendered as such.
     * @param modelFile The .OBJ {@link java.io.File} to read the model data from.
     * @param useDefaultShaders If true, shaders will be set up automatically.
     * @return Returns the {@link wrath.client.graphics.Model} object of your model.
     */
    public static Model loadModel(File modelFile, boolean useDefaultShaders) {
        ArrayList<String> src = readObjFile(modelFile);
        ArrayList<Vector3f> verticies = new ArrayList<>();
        ArrayList<Vector2f> texCoords = new ArrayList<>();
        ArrayList<Vector3f> normals = new ArrayList<>();
        ArrayList<Integer> indicies = new ArrayList<>();
        float[] varray = null;
        float[] narray = null;
        float[] tarray = null;

        boolean tmp = true;
        for (String inp : src) {
            String[] buf = inp.split(" ");
            if (inp.startsWith("v "))
                verticies.add(
                        new Vector3f(Float.parseFloat(buf[1]), Float.parseFloat(buf[2]), Float.parseFloat(buf[3])));
            else if (inp.startsWith("vt "))
                texCoords.add(new Vector2f(Float.parseFloat(buf[1]), Float.parseFloat(buf[2])));
            else if (inp.startsWith("vn "))
                normals.add(
                        new Vector3f(Float.parseFloat(buf[1]), Float.parseFloat(buf[2]), Float.parseFloat(buf[3])));
            else if (inp.startsWith("f ")) {
                if (tmp) {
                    varray = new float[verticies.size() * 3];
                    narray = new float[verticies.size() * 3];
                    tarray = new float[verticies.size() * 2];
                    tmp = false;
                }

                for (int x = 1; x <= 3; x++) {
                    String[] curDat;
                    if (buf[x].contains("//")) {
                        curDat = buf[x].split("//");

                        int ptr = Integer.parseInt(curDat[0]) - 1;
                        indicies.add(ptr);
                        Vector3f norm = normals.get(Integer.parseInt(curDat[1]) - 1);
                        narray[ptr * 3] = norm.x;
                        narray[ptr * 3 + 1] = norm.y;
                        narray[ptr * 3 + 2] = norm.z;
                    } else {
                        curDat = buf[x].split("/");

                        int ptr = Integer.parseInt(curDat[0]) - 1;
                        indicies.add(ptr);
                        Vector2f tex = texCoords.get(Integer.parseInt(curDat[1]) - 1);
                        tarray[ptr * 2] = tex.x;
                        tarray[ptr * 2 + 1] = 1 - tex.y;
                        Vector3f norm = normals.get(Integer.parseInt(curDat[2]) - 1);
                        narray[ptr * 3] = norm.x;
                        narray[ptr * 3 + 1] = norm.y;
                        narray[ptr * 3 + 2] = norm.z;
                    }
                }
            }
        }

        int i = 0;
        for (Vector3f ve : verticies) {
            varray[i] = ve.x;
            varray[i + 1] = ve.y;
            varray[i + 2] = ve.z;
            i += 3;
        }
        int[] iarray = new int[indicies.size()];
        for (int z = 0; z < indicies.size(); z++)
            iarray[z] = indicies.get(z);

        Model m = createModel(modelFile.getName(), varray, iarray, narray, useDefaultShaders);
        m.textureCoords = tarray;
        m.indiciesLen = iarray.length;
        return m;
    }

    private static ArrayList<String> readObjFile(File file) {
        ArrayList<String> src = new ArrayList<>();

        try {
            String inp;
            ArrayList<String> f = new ArrayList<>();
            ArrayList<String> v = new ArrayList<>();
            ArrayList<String> vt = new ArrayList<>();
            ArrayList<String> vn = new ArrayList<>();

            BufferedReader in = new BufferedReader(new FileReader(file));
            while ((inp = in.readLine()) != null) {
                if (inp.startsWith("f "))
                    f.add(inp);
                else if (inp.startsWith("v "))
                    v.add(inp);
                else if (inp.startsWith("vt "))
                    vt.add(inp);
                else if (inp.startsWith("vn "))
                    vn.add(inp);
            }
            in.close();

            v.stream().forEach((s) -> {
                src.add(s);
            });
            vt.stream().forEach((s) -> {
                src.add(s);
            });
            vn.stream().forEach((s) -> {
                src.add(s);
            });
            f.stream().forEach((s) -> {
                src.add(s);
            });
        } catch (IOException e) {
            System.err.println("Could not load model from file '" + file.getName() + "'! I/O Error!");
        }

        return src;
    }

    private final boolean defaultShaders;
    private final int[] indicies;
    private int indiciesLen;
    private final String name;
    private final float[] normals;
    private ShaderProgram shader = null;
    private Texture texture = null;
    private float[] textureCoords = null;
    private int vao;
    private final ArrayList<Integer> vbos = new ArrayList<>();
    private final float[] verticies;

    private Model(String name, int vao, Integer[] initVbos, float[] verticies, int[] indicies, float[] normals,
            boolean defShaders) {
        this.name = name;
        this.vao = vao;
        vbos.addAll(Arrays.asList(initVbos));
        this.verticies = verticies;
        this.normals = normals;
        this.indicies = indicies;
        if (indicies != null)
            indiciesLen = indicies.length;
        this.defaultShaders = defShaders;
    }

    /**
     * Applies a {@link wrath.client.graphics.ShaderProgram} to the model to be called every time the model is rendered.
     * Only one can be attached at a time.
     * @param shader The {@link wrath.client.graphics.ShaderProgram} to associate with this model.
     */
    public void attachShader(ShaderProgram shader) {
        shader.bindAttribute(VERTICIES_ATTRIB_INDEX, "in_Position");
        shader.bindAttribute(NORMALS_ATTRIB_INDEX, "in_Normals");
        if (texture != null)
            shader.bindAttribute(TEXTURE_ATTRIB_INDEX, "in_TextureCoord");
        this.shader = shader;
    }

    /**
     * Applies a {@link wrath.client.graphics.Texture} to the model to be rendered on top of the Model.
     * Only one can be attached at a time.
     * @param texture The {@link wrath.client.graphics.Texture} to associate with this model.
     */
    public void attachTexture(Texture texture) {
        attachTexture(texture, new float[0]);
    }

    /**
     * Applies a {@link wrath.client.graphics.Texture} to the model to be rendered on top of the Model.
     * Only one can be attached at a time.
     * @param texture The {@link wrath.client.graphics.Texture} to associate with this model.
     * @param textureCoords The (u, v) coordinates of the texture to the model.
     */
    public void attachTexture(Texture texture, float[] textureCoords) {
        this.texture = texture;
        if (this.textureCoords != null)
            textureCoords = this.textureCoords;
        if (shader == null)
            Game.getCurrentInstance().getLogger().println(
                    "Warning: If no shader is present to pass texture co-ordinates, then the texture will not render!");
        GL30.glBindVertexArray(vao);
        int vboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboid);
        FloatBuffer vbuffer = BufferUtils.createFloatBuffer(textureCoords.length);
        vbuffer.put(textureCoords);
        vbuffer.flip();
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vbuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(TEXTURE_ATTRIB_INDEX, 2, GL11.GL_FLOAT, false, 0, 0);

        if (shader != null)
            shader.bindAttribute(TEXTURE_ATTRIB_INDEX, "in_TextureCoord");
        GL30.glBindVertexArray(0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        vbos.add(vboid);
        EntityRenderer.preLoadedModels.put(name + "," + texture.getTextureFile().getName(), this);
    }

    @Override
    public void close() {
        GL30.glDeleteVertexArrays(getVaoID());
        vbos.stream().forEach((i) -> {
            GL15.glDeleteBuffers(i);
        });
        vbos.clear();
    }

    /**
     * Gets the {@link wrath.client.graphics.ShaderProgram} attached to this model. 
     * @return Returns the {@link wrath.client.graphics.ShaderProgram} attached to this model.
     */
    public ShaderProgram getShader() {
        return shader;
    }

    /**
     * Gets the {@link wrath.client.graphics.Texture} attached to this model. 
     * @return Returns the {@link wrath.client.graphics.Texture} attached to this model.
     */
    public Texture getTexture() {
        return texture;
    }

    /**
     * Gets the OpenGL ID of the model's Vertex Array Object.
     * @return Returns the OpenGL ID of the model's Vertex Array Object.
     */
    public int getVaoID() {
        return vao;
    }

    /**
     * Gets the OpenGL IDs of the model's Vertex Buffer Objects.
     * @return Returns the OpenGL IDs of the model's Vertex Buffer Objects.
     */
    public Integer[] getVboList() {
        Integer[] ret = new Integer[vbos.size()];
        vbos.toArray(ret);
        return ret;
    }

    /**
     * Gets the amount of verticies in the model.
     * @return Returns the number of verticies in the model.
     */
    public int getVertexCount() {
        return indicies.length;
    }

    @Override
    public void reload() {
        float[] varray = null;
        float[] narray = null;
        int[] iarray;

        File modelFile = new File("assets/models/" + name);
        if (modelFile.exists()) {
            ArrayList<String> src = readObjFile(modelFile);
            ArrayList<Vector3f> verticies = new ArrayList<>();
            ArrayList<Vector2f> texCoords = new ArrayList<>();
            ArrayList<Vector3f> normals = new ArrayList<>();
            ArrayList<Integer> indicies = new ArrayList<>();
            float[] tarray = null;

            boolean tmp = true;
            for (String inp : src) {
                String[] buf = inp.split(" ");
                if (inp.startsWith("v "))
                    verticies.add(new Vector3f(Float.parseFloat(buf[1]), Float.parseFloat(buf[2]),
                            Float.parseFloat(buf[3])));
                else if (inp.startsWith("vt "))
                    texCoords.add(new Vector2f(Float.parseFloat(buf[1]), Float.parseFloat(buf[2])));
                else if (inp.startsWith("vn "))
                    normals.add(new Vector3f(Float.parseFloat(buf[1]), Float.parseFloat(buf[2]),
                            Float.parseFloat(buf[3])));
                else if (inp.startsWith("f ")) {
                    if (tmp) {
                        varray = new float[verticies.size() * 3];
                        narray = new float[verticies.size() * 3];
                        tarray = new float[verticies.size() * 2];
                        tmp = false;
                    }

                    for (int x = 1; x <= 3; x++) {
                        String[] curDat;
                        if (buf[x].contains("//")) {
                            curDat = buf[x].split("//");

                            int ptr = Integer.parseInt(curDat[0]) - 1;
                            indicies.add(ptr);
                            Vector3f norm = normals.get(Integer.parseInt(curDat[1]) - 1);
                            narray[ptr * 3] = norm.x;
                            narray[ptr * 3 + 1] = norm.y;
                            narray[ptr * 3 + 2] = norm.z;
                        } else {
                            curDat = buf[x].split("/");

                            int ptr = Integer.parseInt(curDat[0]) - 1;
                            indicies.add(ptr);
                            Vector2f tex = texCoords.get(Integer.parseInt(curDat[1]) - 1);
                            tarray[ptr * 2] = tex.x;
                            tarray[ptr * 2 + 1] = 1 - tex.y;
                            Vector3f norm = normals.get(Integer.parseInt(curDat[2]) - 1);
                            narray[ptr * 3] = norm.x;
                            narray[ptr * 3 + 1] = norm.y;
                            narray[ptr * 3 + 2] = norm.z;
                        }
                    }
                }
            }

            int i = 0;
            for (Vector3f ve : verticies) {
                varray[i] = ve.x;
                varray[i + 1] = ve.y;
                varray[i + 2] = ve.z;
                i += 3;
            }
            iarray = new int[indicies.size()];
            for (int z = 0; z < indicies.size(); z++)
                iarray[z] = indicies.get(z);

            textureCoords = tarray;
            indiciesLen = iarray.length;
        } else {
            varray = verticies;
            narray = normals;
            iarray = indicies;
            indiciesLen = indicies.length;
        }

        // Generating VAO
        vao = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vao);

        // Generating Verticies VBO
        int vtvboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vtvboid);
        FloatBuffer vbuffer = BufferUtils.createFloatBuffer(varray.length);
        vbuffer.put(varray);
        vbuffer.flip();
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vbuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(VERTICIES_ATTRIB_INDEX, 3, GL11.GL_FLOAT, false, 0, 0);

        // Generating Normals VBO
        int nmvboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, nmvboid);
        FloatBuffer nbuffer = BufferUtils.createFloatBuffer(narray.length);
        nbuffer.put(narray);
        nbuffer.flip();
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, nbuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(NORMALS_ATTRIB_INDEX, 3, GL11.GL_FLOAT, false, 0, 0);

        // Generating Indicies VBO
        int invboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, invboid);
        IntBuffer ibuffer = BufferUtils.createIntBuffer(iarray.length);
        ibuffer.put(iarray);
        ibuffer.flip();
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ibuffer, GL15.GL_STATIC_DRAW);

        // Generating Texture VBO
        int texvboid = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, texvboid);
        FloatBuffer tbuffer = BufferUtils.createFloatBuffer(textureCoords.length);
        tbuffer.put(textureCoords);
        tbuffer.flip();
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, tbuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(TEXTURE_ATTRIB_INDEX, 2, GL11.GL_FLOAT, false, 0, 0);

        // Creating Model Object
        vbos.add(vtvboid);
        vbos.add(invboid);
        vbos.add(nmvboid);
        vbos.add(texvboid);
        Game.getCurrentInstance().getLogger().println("Reloaded model '" + name + "'!");
        if (defaultShaders)
            this.attachShader(ShaderProgram.DEFAULT_SHADER);

        // Unbinding OpenGL Objects
        GL30.glBindVertexArray(0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
    }

    @Override
    public void render(boolean consolidated) {
        if (!shader.isFinalized())
            shader.finish();
        if (consolidated)
            renderSetup();

        GL11.glDrawElements(GL11.GL_TRIANGLES, indiciesLen, GL11.GL_UNSIGNED_INT, 0);

        if (consolidated)
            renderStop();
    }

    @Override
    public void renderSetup() {
        GL30.glBindVertexArray(vao);
        GL20.glEnableVertexAttribArray(VERTICIES_ATTRIB_INDEX);
        GL20.glEnableVertexAttribArray(NORMALS_ATTRIB_INDEX);
        if (texture != null) {
            GL20.glEnableVertexAttribArray(TEXTURE_ATTRIB_INDEX);
            texture.bindTexture();
        }

        if (shader != null) {
            shader.updateViewMatrix();
            shader.bindShader();
        }
    }

    @Override
    public void renderStop() {
        ShaderProgram.unbindShaders();
        if (texture != null)
            GL20.glDisableVertexAttribArray(TEXTURE_ATTRIB_INDEX);
        Texture.unbindTextures();
        GL20.glDisableVertexAttribArray(VERTICIES_ATTRIB_INDEX);
        GL20.glDisableVertexAttribArray(NORMALS_ATTRIB_INDEX);
        GL30.glBindVertexArray(0);
    }
}