package glmodel;

import org.lwjgl.opengl.GL11;

/** 
 *  This class loads and renders a mesh from an OBJ file format.  The mesh can have
 *  multiple materials, including texture images.
 *
 *  Uses GL_Mesh to load a .obj file and GLMaterialLIb to load the .mtl file.  It
 *  assumes the .obj .mtl and any texture images are present in the same folder.
 *
 *  Also has a function, renderTextured() to draw a mesh with no groups or materials.
 *  The entire mesh will be drawn as one group of triangles with one texture.
 */
public class GLModel {
    // a default material to use if none is specified
    public static GLMaterial defaultMtl = new GLMaterial();

    public GL_Mesh mesh;                 // holds vertex, triangle and group information
    public int displayListID;

    // !!!! this will be null. NEED to check this in OBJ_Reader and check renderGroups() (BROKEN!!!)
    public GLMaterial[] groupMaterials;  // holds material for each face group (1 group to 1 material)

	public GLModel(String filename) {
		// load OBJ file
		mesh = loadMesh(filename);
	}

	/**
	 * read the given .obj file into a GL_Mesh object
	 * @param filename (must end in .obj)
	 * @return the loaded GL_Mesh
	 */
    public GL_Mesh loadMesh(String filename) {
    	if (filename.toUpperCase().endsWith(".OBJ")) {
    		GL_OBJ_Importer importer = new GL_OBJ_Importer();
    		mesh = importer.load(filename);
    	}
    	else {
    		GL_3DS_Importer importer = new GL_3DS_Importer();
    		mesh = importer.load(filename);
    		System.out.println("GLMeshRenderer.loadMesh(): WARNING 3DS files functionality is limited");
    	}
    	return mesh;
    }

    /**
     * Render mesh into a displaylist and store the listID in the flowercenter object.
     * @param PR   PetalRing to draw
     */
	public void makeDisplayList() {
		// call the displaylist
		if (displayListID == 0) {
			displayListID = GL11.glGenLists(1); // allocate a display list
			GL11.glNewList(displayListID, GL11.GL_COMPILE);    // Start the list
			render(mesh);      // render the mesh
			GL11.glEndList();                     // done
		}
	}

	/**
	 * return the display list ID created by makeDisplayList()
	 */
	public int getDisplayListID() {
		return displayListID;
	}

	/**
	 * recalculate normals on the mesh object
	 * @see GL_Mesh.regenerateNormals()
	 * @see GL_Mesh.setSmoothingAngle()
	 */
	public void regenerateNormals() {
		if (mesh != null) {
			mesh.regenerateNormals();
		}
	}
	/**
	 *  Draw the model.
	 *  Calls the displaylist if one is created, or calls renderGroups()
	 */
	public void render() {
		if (displayListID == 0) {
			render(mesh);
		}
		else {
			GL11.glCallList(displayListID);
		}
	}
	
	/**
	 * Draw one group from the mesh.  This will activate the
	 * correct material for the group (including textures).
	 * @param groupName  name of group (from obj file)
	 */
	public void renderGroup(String groupName)
	{
		int GID = -1;  // group id
		
		// find group by name
		for (int g=0; g < mesh.numGroups(); g++) {
			if (mesh.getGroupName(g).equals(groupName)) {
				GID = g;
				break;
			}
		}
		if (GID == -1) {
			return;
		}
		
		// draw the triangles in this group
		GLMaterial[] materials = mesh.materials;   // loaded from the .mtl file
		GL_Triangle[] triangles = mesh.getGroupFaces(GID);  // each group may have a material
		GLMaterial mtl;
		GL_Triangle t;
		int currMtl = -1;
		int i = 0;
		
		// draw all triangles in object
		for (i=0; i < triangles.length; ) {
			t = triangles[i];
			
			// activate new material and texture
			currMtl = t.materialID;
			mtl = (materials != null && materials.length>0 && currMtl >= 0)? materials[currMtl] : defaultMtl;
			mtl.apply();
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, mtl.textureHandle);
			
			// draw triangles until material changes
			GL11.glBegin(GL11.GL_TRIANGLES);
			for ( ; i < triangles.length && (t=triangles[i])!=null && currMtl == t.materialID; i++) {
				GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
				GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
				GL11.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z);
				
				GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
				GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
				GL11.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z);
				
				GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
				GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
				GL11.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z);
			}
			GL11.glEnd();
		}
	}

    /**
     * This is a simple way to render a mesh with no materials.  Draws
     * the mesh with normals and texture coordinates.  Loops through
     * all triangles in the mesh object (ignores groups and materials).
     *
     * @param o  mesh object to render
     */
    public void renderTextured(int textureHandle)
    {
        GL_Triangle t;
        GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle);
        GL11.glBegin(GL11.GL_TRIANGLES);
        for (int j = 0; j < mesh.triangles.length; j++) { // draw all triangles in object
            t = mesh.triangles[j];

            GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
            GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
            GL11.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z);

            GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
            GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
            GL11.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z);

            GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
            GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
            GL11.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z);
        }
        GL11.glEnd();
    }

    /**
     * Render a mesh with materials.  If no materials exist (none are defined
     * in the mesh, or the materials file was not found), then a default material
     * will be applied and texture 0 will be activated (see GLMaterial.java for 
     * the default material settings).
     */
    public void render(GL_Mesh m)
    {
        GLMaterial[] materials = m.materials;   // loaded from the .mtl file
        GLMaterial mtl;
        GL_Triangle t;
        int currMtl = -1;
        int i = 0;

        // draw all triangles in object
        for (i=0; i < m.triangles.length; ) {
            t = m.triangles[i];

            // activate new material and texture
            currMtl = t.materialID;
            mtl = (materials != null && materials.length>0 && currMtl >= 0)? materials[currMtl] : defaultMtl;
            mtl.apply();
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, mtl.textureHandle);

            // draw triangles until material changes
            GL11.glBegin(GL11.GL_TRIANGLES);
            for ( ; i < m.triangles.length && (t=m.triangles[i])!=null && currMtl == t.materialID; i++) {
                GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y);
                GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z);
                GL11.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z);

                GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y);
                GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z);
                GL11.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z);

                GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y);
                GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z);
                GL11.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z);
            }
            GL11.glEnd();
        }
    }
    
    public void renderMeshNormals()
    {
        GL_Triangle t;
        GL11.glDisable(GL11.GL_LIGHTING);
        GL11.glColor3f(0,1,0);
        GL11.glBegin(GL11.GL_LINES);
        {
        	for (int j = 0; j < mesh.triangles.length; j++) { // draw all triangles in object
        		t = mesh.triangles[j];
        		t.norm1.normalize();
        		t.norm2.normalize();
        		t.norm3.normalize();
        		
        		GL11.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z);
        		GL11.glVertex3f( (float)(t.p1.pos.x+t.norm1.x), (float)(t.p1.pos.y+t.norm1.y), (float)(t.p1.pos.z+t.norm1.z));
        		
        		GL11.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z);
        		GL11.glVertex3f( (float)(t.p2.pos.x+t.norm2.x), (float)(t.p2.pos.y+t.norm2.y), (float)(t.p2.pos.z+t.norm2.z));
        		
        		GL11.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z);
        		GL11.glVertex3f( (float)(t.p3.pos.x+t.norm3.x), (float)(t.p3.pos.y+t.norm3.y), (float)(t.p3.pos.z+t.norm3.z));
        	}
        }
        GL11.glEnd();
        GL11.glEnable(GL11.GL_LIGHTING);
    }

}
