package glmodel;

import java.io.*;
import java.util.ArrayList;

/*
 * Read an OBJ file and load it into a GL_Mesh.  GL_Mesh is a basic
 * mesh object that holds vertex and triangle data.  The GL_OBJ_Reader
 * loads data pretty much as it is in the OBJ, and this Importer converts
 * the idiosyncracies of the OBJ format into a straightforward vert/triangle
 * structure.
 *
 * NOTE: OBJ files can contain polygon faces. The GL_Mesh holds only
 * triangles, so all faces will be converted to triangles.
 *
 * jun 2006: in makeMeshObject() store uv coord with each vertex.
 * jul 2006: preserve face groups from the obj.
 */
public class GL_OBJ_Importer {
	private GL_OBJ_Reader reader = null;
	private GL_Mesh mesh = null;

	public GL_OBJ_Importer() {
	}

    public GL_Mesh load(String filename) {
        System.out.println("GL_OBJ_Importer.import(): Load object from OBJ " + filename);
        reader = new GL_OBJ_Reader(filename);
        System.out.println("GL_OBJ_Importer.importFromStream(): model has " + reader.faces.size() + " faces and " + reader.vertices.size() + " vertices.  Mtl file is " + reader.materialLibeName);
        return makeMeshObject(reader);
    }

    public GL_Mesh importFromStream(InputStream inStream) {
        System.out.println("GL_OBJ_Importer.importFromStream(): Load object from OBJ...");
        reader = new GL_OBJ_Reader(inStream);
        System.out.println("GL_OBJ_Importer.importFromStream(): model has " + reader.faces.size() + " faces and " + reader.vertices.size() + " vertices.  Mtl file is " + reader.materialLibeName);
        return makeMeshObject(reader);
    }

	/**
	 * create a GL_Mesh (mesh object) from the data read by a GL_OBJ_Reader
	 */
    public GL_Mesh makeMeshObject(GL_OBJ_Reader objData) {
        ArrayList verts = objData.vertices;
        ArrayList txtrs = objData.textureCoords;
        ArrayList norms = objData.normals;
        ArrayList faces = objData.faces;

        // make a new mesh
        mesh = new GL_Mesh();
        mesh.name = objData.filename;
        mesh.materialLibeName = objData.materialLibeName;
        mesh.materials = (objData.materialLib != null)? objData.materialLib.materials : null;

        // add verts to GL_Mesh
        for (int i = 0; i < verts.size(); i++) {
            float[] coords = (float[]) verts.get(i);
			mesh.addVertex(coords[0], coords[1], coords[2]);
        }

        // allocate space for groups
        mesh.makeGroups(objData.numGroups());

        // init each group (allocate space for triangles)
        for (int g = 0; g < objData.numGroups(); g++) {
            mesh.initGroup(g,
                          objData.getGroupName(g),
                          objData.getGroupMaterialName(g),
                          objData.getGroupTriangleCount(g));
        }

        // add triangles to GL_Mesh.  OBJ "face" may be a triangle,
        // quad or polygon.  Convert all faces to triangles.
        for (int g = 0; g < objData.numGroups(); g++) {
            int triCount=0;
            faces = objData.getGroupFaces(g);
            for (int i = 0; i < faces.size(); i++) {
                Face face = (Face) faces.get(i);
                // put verts, normals, texture coords into triangle(s)
                if (face.vertexIDs.length == 3) {
                    addTriangle(mesh, g, triCount, face, txtrs, norms, 0, 1, 2, face.materialID);
                    triCount++;
                }
                else if (face.vertexIDs.length == 4) {
                    // convert quad to two triangles
                    addTriangle(mesh, g, triCount, face, txtrs, norms, 0, 1, 2, face.materialID);
                    triCount++;
                    addTriangle(mesh, g, triCount, face, txtrs, norms, 0, 2, 3, face.materialID);
                    triCount++;
                }
                else {
                    // convert polygon to triangle fan, with first vertex (0)
                    // at center:  0,1,2   0,2,3   0,3,4   0,4,5
                    for (int n = 0; n < face.vertexIDs.length - 2; n++) {
                        addTriangle(mesh, g, triCount, face, txtrs, norms, 0, n + 1, n + 2, face.materialID);
                        triCount++;
                    }
                }
            }
        }

		// optimize the GL_Mesh
		mesh.rebuild();

        // if no normals were loaded, generate some
        if (norms.size() == 0) {
        	mesh.regenerateNormals();
        }

		return mesh;
	}

    /**
     * Add a new triangle to the GL_Mesh.  This assumes that the
     * vertices have already been added to the GL_Mesh, in the same
     * order that they were in the OBJ.  Also the mesh has groups allocated
     * with triangle arrays.
     *
     * @param obj       GL_Mesh
     * @param groupNum  the group to add the triangles to
     * @param triNum    the index of the triangle in the group
     * @param face      a face from the OBJ file
     * @param txtrs     ArrayList of texture coords from the OBJ file
     * @param norms     ArrayList of normals from the OBJ file
     * @param v1        vertices to use for the triangle (face may have >3 verts)
     * @param v2
     * @param v3
     * @return
     */
    public GL_Triangle addTriangle(GL_Mesh obj, int groupNum, int triNum, Face face,
                                   ArrayList txtrs, ArrayList norms,
                                   int v1, int v2, int v3, int mtlID) {
        // An OBJ face may have many vertices (can be a polygon).
        // Make a new triangle with the specified three verts.
        GL_Triangle t = new GL_Triangle(
            obj.vertex(face.vertexIDs[v1]),
            obj.vertex(face.vertexIDs[v2]),
            obj.vertex(face.vertexIDs[v3]));

        // put texture coords into triangle
        if (txtrs.size() > 0) { // if texture coords were loaded
            float[] uvw;
            uvw = (float[]) txtrs.get(face.textureIDs[v1]); // txtr coord for vert 1
            t.uvw1 = new GL_Vector(uvw[0], uvw[1], uvw[2]);
            uvw = (float[]) txtrs.get(face.textureIDs[v2]); // txtr coord for vert 2
            t.uvw2 = new GL_Vector(uvw[0], uvw[1], uvw[2]);
            uvw = (float[]) txtrs.get(face.textureIDs[v3]); // txtr coord for vert 3
            t.uvw3 = new GL_Vector(uvw[0], uvw[1], uvw[2]);
        }

        // put normals into triangle (NOTE: normalID can be -1!!! could barf here!!!)
        if (norms.size() > 0) { // if normals were loaded
            float[] norm;
            norm = (float[]) norms.get(face.normalIDs[v1]); // normal for vert 1
            t.norm1 = new GL_Vector(norm[0], norm[1], norm[2]);
            norm = (float[]) norms.get(face.normalIDs[v2]); // normal for vert 2
            t.norm2 = new GL_Vector(norm[0], norm[1], norm[2]);
            norm = (float[]) norms.get(face.normalIDs[v3]); // normal for vert 3
            t.norm3 = new GL_Vector(norm[0], norm[1], norm[2]);
        }

	// store material number in triangle
	t.materialID = mtlID;

        // add triangle to given group in GL_Mesh
        obj.addTriangle(t, groupNum, triNum);

        return t;
    }
}