package meshTools;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import java.math.*;


/**
 * Class implementing the Loop subdivision scheme
 */
public class Loop {


    /**
     * Subdivides the provided half edge data structure
     * @param heds the mesh to subdivide
     * @return the subdivided mesh
     */
    public static HEDS subdivide( HEDS heds ) {
    	
    	
      
        heds=evenVertices(heds);
        heds=oddVertices(heds);
 
        //connect things up
        heds=connectUp(heds);
        //load faces from heds to heds2
        HEDS heds2=loadFaces(heds);
        //khomhpyutte the normals
       //heds2=compNormals(heds2);
        
 
        
        // TODO finish this method!!
        // you will certainly want to write lots of helper methods!
      
        
        return heds2;        
    }
    
    //this method computes normals
public static HEDS compNormals(HEDS heds2){
    	HalfEdge aHE;
    	HalfEdge stepVert;

    	Vector3d tempVert= new Vector3d();
    	Vector3d tangentOne= new Vector3d();
    	Vector3d tangentTwo= new Vector3d();
    	double valence;
    	double vertCount;
    	Vector3d theNormal;
	//go to each face
	for(HalfEdge he: heds2.faces){
		aHE=he;
		//go to each edge's head in a face		
		do{
			if (aHE.head.n==null){
				stepVert=aHE;
				//valence=getValence(aHE);
				vertCount=0;
				tangentOne=new Vector3d();
				tangentTwo=new Vector3d();
				theNormal=new Vector3d();
				//if the half edge points to a vertex on a boundary
				if(aHE.head.isOnBound){
					//first back up to the first edge on the boundary
					while(stepVert.twin!=null){
						stepVert=stepVert.twin.next.next;
					}
					if(stepVert.next.twin==null){
						//get the single triangle if there is only one
						tangentOne.scaleAdd(-1, stepVert.head.p, stepVert.next.next.head.p);
						tangentOne.normalize();
						tangentTwo.scaleAdd(-1, stepVert.head.p,stepVert.next.head.p);
						tangentTwo.normalize();
						tempVert.cross(tangentOne, tangentTwo);
						theNormal.add(tempVert);
					}else{
						//now go forward, collecting all the normals until you reach the next boundary
						while(stepVert.next.twin!=null){						
							tangentOne.scaleAdd(-1, stepVert.head.p, stepVert.next.next.head.p);
							tangentOne.normalize();
							tangentTwo.scaleAdd(-1, stepVert.head.p,stepVert.next.head.p);
							tangentTwo.normalize();
							tempVert.cross(tangentOne, tangentTwo);
							theNormal.add(tempVert);
							stepVert=stepVert.next.twin;
						}	
					}
					theNormal.normalize();
				}else{
					//go to all adjacent vertices around a head
					do{
						stepVert=stepVert.twin;
						tangentOne.set(stepVert.head.p.x, stepVert.head.p.y, stepVert.head.p.z);
						tangentOne.scaleAdd(-1, aHE.head.p);
						tangentOne.normalize();
						stepVert=stepVert.next;
						tangentTwo.set(stepVert.head.p);
						tangentTwo.scaleAdd(-1, aHE.head.p);
						tangentTwo.normalize();
						tempVert.cross(tangentOne, tangentTwo);
						theNormal.add(tempVert);
						stepVert=stepVert.next;
						vertCount=vertCount+1;
					}while(stepVert!=aHE);	
					//theNormal.cross( tangentOne, tangentTwo);
					theNormal.normalize();
					aHE.head.n=theNormal;
				}
			}
			aHE=aHE.next;
		} while(aHE!=he);
	}	
	return heds2;
}
    
//this sub calculates the valence
private static double getValence(HalfEdge anEdge){
	double theValence=0d;
	HalfEdge stepEdge=new HalfEdge();
	stepEdge=anEdge;
	do{
		stepEdge=stepEdge.twin;
		theValence=theValence+1;
		stepEdge=stepEdge.next.next;
	}while(stepEdge!=anEdge);
	return theValence;
}

    
    //this is a sub to load in a faces list to the next subdivision level.  It 
    //adds the child1 of each face (this is one edge of one of the three new child faces)
    //It also adds the next.twin of child1 of each edge on a face and that edge forms the face
    //of the new interior face.
private static HEDS loadFaces(HEDS heds){
	HEDS theHeds = new HEDS();
	HalfEdge anEdge= new HalfEdge();
	//go to each face
	for(HalfEdge he : heds.faces){		
		anEdge=he;
		//this adds one edge from the interior child face which shares no edges with the original 
		//parent face.
		theHeds.faces.add(he.child1.next.twin);
		//go around a face
		do{	
			theHeds.faces.add(anEdge.child1);
			anEdge=anEdge.next;
		}
		while(anEdge!= he);
		 
	}
	
	return theHeds;
}
    
//this sub connects up the edges and thereby creates the six inner edges
//
private static HEDS connectUp(HEDS heds){
	 //now we want to connect up the edges we have so far
	HalfEdge anEdge= new HalfEdge();
	//go to each face
	for(HalfEdge he: heds.faces){
		anEdge=he;
		//go around a face
		do{
			//connect at the corners of a face
			if(anEdge.child2.next==null){
				anEdge.child2.next=anEdge.next.child1;
			}
			//connect the child1 (via next) to the new edge (1) across the middle 
			//and create its twin (2)
			if(anEdge.child1.next==null){
				anEdge.child1.next=new HalfEdge();
				anEdge.child1.next.head=anEdge.next.next.child1.head;
				anEdge.child1.next.next=anEdge.next.next.child2;
				anEdge.child1.next.twin= new HalfEdge();
				anEdge.child1.next.twin.head=anEdge.child1.head;
				anEdge.child1.next.twin.twin=anEdge.child1.next;
			}
			anEdge=anEdge.next;
		}
			while(anEdge!=he);
		//this leaves only the connection of the three edges of the interior
		//face.  They have to point at each other.
		
		he.child1.next.twin.next=he.next.child1.next.twin;	
		he.next.child1.next.twin.next=he.next.next.child1.next.twin;
		he.next.next.child1.next.twin.next=he.child1.next.twin;
	}
	return heds;
}	



// method to caclulate even vertices
private static HEDS evenVertices(HEDS heds){
	HalfEdge aHE=new HalfEdge();
	Vertex newVert= new Vertex();
	HalfEdge he=new HalfEdge();
	double vertCount;
	double beta;
	Vertex scaledVert=new Vertex();
	double valence=0;
	//around all faces
	for(HalfEdge currentEdge : heds.faces){
		he=null;
	        //around all edges in a face
			while(he!=currentEdge){
			 	newVert=new Vertex();
				scaledVert=new Vertex();
				if(he==null)
					he=currentEdge;
				vertCount=0;
				aHE=null;
				//check if the vertex is on the boundary
				if(he.head.isOnBound){
					if(aHE==null)
						aHE=he;
					scaledVert.p.set(currentEdge.head.p);
					scaledVert.p.scale(0.75);
					scaledVert.p.scaleAdd(1d/8d, currentEdge.next.next.head.p, scaledVert.p);
					while(aHE.next.twin!=null){
						aHE=aHE.next.twin;
					}
					scaledVert.p.scaleAdd(1d/8d, he.next.head.p, scaledVert.p);
					scaledVert.isOnBound=true;
					aHE.head.child=scaledVert;					
				}
				else{
					if(he.head.child==null){
						//around all the vertices coming off of one vertex
						while(aHE!=he){
							if(aHE==null)
								aHE=he.twin;
							else
								aHE=aHE.twin;
							newVert.p.add((Point3d)aHE.head.p.clone());
							aHE=aHE.next.next;
							vertCount=vertCount+1;
						}
						double PI=3.1415926535897932384626433;
						valence=getValence(he);
						beta=(1/vertCount)*(5d/8d -Math.pow((3d/8d+1d/4d)*Math.cos(2d*Math.PI/vertCount),2));
						newVert.p.scale((beta));
					   	scaledVert.p=(Point3d) he.head.p.clone();
				    	scaledVert.p.scale(1.0d-vertCount*beta);
				    	newVert.p.add(scaledVert.p);
				    	newVert.isOnBound=false;
				    	he.head.child=newVert;
					}
				}
			    he=he.next;
		}
		

	    	
	}
	return heds;
}

    //a method to calculate the odd child vertices in the HEDS


private static HEDS oddVertices(HEDS heds){
	Vertex scaledVert;
	Vertex newVert;
	newVert= new Vertex();
	scaledVert=new Vertex();
	HalfEdge he;
	//loop over all faces
	for(HalfEdge anEdge: heds.faces){
		he=null;
		//around all edges in a face
		while(he!=anEdge){
			if(he==null)
				he=anEdge;
			if(he.head.isOnBound&&he.next.next.head.isOnBound){
				//use the loop method for a boundary vertex
				scaledVert.p=he.head.p;
				scaledVert.p.scale(0.5d);
				scaledVert.p.scaleAdd(0.5d,he.next.next.head.p,scaledVert.p);
				newVert.p.set(scaledVert.p);				
				he.child1=new HalfEdge();
				he.child1.head=new Vertex();
				he.child2=new HalfEdge();
				he.child2.head=new Vertex();
				he.child1.head.isOnBound=true;
				he.child2.head.isOnBound=true;
				he.child1.head.p.set(newVert.p);
				he.child1.head.isOnBound=true;
				he.child1.parent=he;
				he.child2.head=he.head.child;
				he.child2.parent=he;
				he.child2.head.isOnBound=true;
			}else{
				if(he.child1==null){
					scaledVert.p=(Point3d)he.head.p.clone();
					scaledVert.p.scale((3.0d/8.0d));
					newVert.p.add(scaledVert.p);
					scaledVert.p=(Point3d) he.next.head.p.clone();
					scaledVert.p.scale((1.0d/8.0d));
					newVert.p.add(scaledVert.p);
					scaledVert.p=(Point3d)he.next.next.head.p.clone();
					scaledVert.p.scale((3.0d/8.0d));
					newVert.p.add(scaledVert.p);
					scaledVert.p=(Point3d)he.twin.next.head.p.clone();
					scaledVert.p.scale((1.0d/8.0d));
					newVert.p.add(scaledVert.p);
					he.child1=new HalfEdge();
					he.child1.head=new Vertex();
					he.child2=new HalfEdge();
					he.child2.head=new Vertex();
					he.child1.head.isOnBound=false;
					he.child2.head.isOnBound=false;
					//heds.faces(1).next.next goes wrong
					he.child1.head.p=(Point3d) newVert.p.clone();	
					he.child1.parent=he;
					he.child2.head=he.head.child;
					he.child2.parent=he;
					he.twin.child1=new HalfEdge();
					he.twin.child1.head=new Vertex();
					he.twin.child2=new HalfEdge();
					he.twin.child2.head=new Vertex();
					he.twin.child1.head.isOnBound=false;
					if(he.next.next.head.isOnBound){
						he.twin.child2.head.isOnBound=true;
					}else he.twin.child2.head.isOnBound=false;
					he.twin.child1.head.p=(Point3d) newVert.p.clone();
					he.twin.child1.parent=he.twin;
					he.twin.child2.head=he.twin.head.child;
					he.twin.child2.parent=he.twin;
					he.child1.twin=he.twin.child2;
					he.child2.twin=he.twin.child1;
					he.twin.child1.twin=he.child2;
					he.twin.child2.twin=he.child1;
					he.twin.child1.next=he.twin.child2;
				}	
			}
			he=he.next;
		}
			
			
	}
	
	
	return heds;
}



}

