import java.awt.*;
/** 
 * Premica v 3D prostoru. 
 */
public class Premica {

	/**
	 * Vektor prve tocke na premici. 
	 */
	public Vektor r1;
	
	/**
	 * Vektor druge tocke na premici. 
	 */
	public Vektor r2;
	
	/** 
	 * Smerni enotski vektor premice. 
	 */
	public Vektor nR;
	
	/**
	 * Prva prebodna tocka premice z mejnim kvadrom. 
	 */
	public Tocka A;
	
	/**
	 * Druga prebodna tocka premice z mejnim kvadrom. 
	 */
	public Tocka B;
	
	/**
	 * Prebodna tocka premice s koordinatno ravnino. Ime tocke 
	 * ustreza koordinatni ravnini.
	 */
	public Tocka XY,XZ,YZ;
	
	/**
	 * Prebodne tocke premice z ravninami mejnega kvadra.
	 * Indeksi tock ustrezajo indeksom ravnin mejnega kvadra 
	 * v polju <a href="Rhcs.html#rv">rv</a> v razredu Rhcs. 
	 * Ce ni preboda z ravnino, je na tem mestu <code>null</code>.
     */
	public Tocka pt[];
	
	/**
     * Vidne prebodne tocke premice z ravninami mejnega kvadra. 
     * Ce ni preboda ali ce tocka ne lezi znotraj 
     * <a href="Ravnina.html#op">poligona</a> ravnine,
     * je na tem mestu <code>null</code>.
	 */
	public Tocka ab[];
	
	/**
	 * Doloci premico z znanimi projekcijami na koordinatnih ravninah. 
	 * Koordinatni sistem <code>cs</code> mora imeti definirane 
	 * ravnine mejnega kvadra in koordinatne ravnine kot 
	 * objekt razreda <a href="Ravnina.html">Ravnina</a>.
	 *@param x1  x koordinata prve tocke na premici
	 *@param y1  y koordinata prve tocke na premici
	 *@param z1  z koordinata prve tocke na premici
	 *@param x2  x koordinata druge tocke na premici
	 *@param y2  y koordinata druge tocke na premici
	 *@param z2  z koordinata druge tocke na premici
	 *@param cs  3D model koordinatnega sistema
	 */
	public Premica(float x1,float y1,float z1,
			       float x2,float y2,float z2,Rhcs cs) {
		this(new Tocka(x1,y1,z1),new Tocka(x2,y2,z2),cs);
	}
	
	/**
	 * Doloci premico s tocko in smernim vektorjem premice. 
	 * Koordinatni sistem <code>cs</code> mora imeti definirane 
	 * ravnine mejnega kvadra in koordinatne ravnine kot 
	 * objekt razreda <a href="Ravnina.html">Ravnina</a>.
	 *@param A  tocka na premici
	 *@param V  smerni vektor premice
	 *@param cs  3D model koordinatnega sistema
	 */
	public Premica(Tocka A,Vektor V,Rhcs cs) {
		this(A,new Tocka(A.x + V.x, A.y + V.y, A.z + V.z),cs);
	}
	
	/**
	 * Doloci premico skozi dve tocki. Koordinatni sistem <code>cs</code> 
	 * mora imeti definirane ravnine mejnega kvadra in koordinatne 
	 * ravnine kot objekt razreda <a href="Ravnina.html">Ravnina</a>.
	 *@param T1  prva tocka na premici
	 *@param T2  druga tocka na premici
	 *@param cs  3D model koordinatnega sistema
	 */
	public Premica (Tocka T1,Tocka T2,Rhcs cs) {  
		r1 = new Vektor(T1);	// krajevni vektor tocke T1
		r2 = new Vektor(T2);	// krajevni vektor tocke T2
    	nR = new Vektor(T1,T2); // enotski vektor med tockama
		nR.normVekt();

		// tocki preboda mejnega kvadra (A in B):
		ab = intersec(cs.rv); // vse tocke preboda mejnega kvadra
		// vzame prvo tocko preboda:
		for(int i=0; i<ab.length; i++) {
	  	   	if(ab[i] != null) {
	  	   		A = new Tocka(ab[i]);
	  	   		break;
	  	   	}
		}
		// poisce drugo tocko preboda:
		for(int i=0; i<ab.length; i++) {
	  	   	if(ab[i] != null && !A.equals(ab[i])) {
	  	   		B = new Tocka(ab[i]);
	  	   		break;
	  	   	}
		}
		// tocke preboda koordinatnih ravnin:
		XZ = intersec(cs.xz);
		XY = intersec(cs.xy);
		YZ = intersec(cs.yz);
	}

	/**
	 * Izracuna tocko preboda premice z ravnino. Vrne vidno tocko 
	 * preboda ali <code>null</code>, ce ni preboda ali ce tocka
	 * ne lezi znotraj <a href="Ravnina.html#op">poligona</a> ravnine
	 * (tocka ni vidna).
	 *@param rv  ravnina
	 */
	public Tocka intersec(Ravnina rv) {
		Vektor tr1,tr2;
		Tocka tT = new Tocka();
		Matrix3D mat = new Matrix3D();
		double alfa,beta;
		double d = Math.sqrt(rv.n.y * rv.n.y + rv.n.z * rv.n.z);
		
		if(d == 0) alfa = 0;
		else { alfa = (rv.n.z >= 0) ? Math.asin(rv.n.y / d) * 180 / Math.PI :
    	         			    180 - Math.asin(rv.n.y / d) * 180 / Math.PI;
		}
  		beta = Math.asin(-rv.n.x) * 180 / Math.PI;
		
		// matrika za transformacijo ravnine rv v ravnino xy:
	 	mat.unit();
		mat.translate(-rv.V.x,-rv.V.y,-rv.V.z);
		mat.xrot(-alfa); // Matrix3D !
		mat.yrot(beta);
      
		//	transformacija premice:
		tr1 = mat.transform(r1);
		tr2 = mat.transform(r2);
		
		float Rz = tr2.z - tr1.z;

		if( Rz < -1.e-5 || 1.e-5 < Rz ) { // premica ni vzporedna ravnini

		 	// izracun tocke preboda ravnine xy:
			tT.x = tr1.x - tr1.z * (tr2.x - tr1.x) / Rz;
			tT.y = tr1.y - tr1.z * (tr2.y - tr1.y) / Rz;
			tT.z = 0.f;

		 	// inverzna transformacija (xy -> rv):
		 	mat.unit();
			mat.yrot(-beta);
			mat.xrot(alfa);
			mat.translate(rv.V.x,rv.V.y,rv.V.z);
			
		 	// vrne tocko preboda, ce lezi v poligonu:
			if(rv.inside(mat.transform(tT))) {
			 	return (mat.transform(tT));
	 	    }
	 	}
	 	return null; // ni preboda
	}

	/**
	 * Izracuna prebode premice z ravninami. Vrne enako veliko 
	 * polje vidnih prebodnih tock.Ce ni preboda ali ce tocka ne 
	 * lezi znotraj <a href="Ravnina.html#op">poligona</a> ravnine,
     * je na tem mestu <code>null</code>. Indeksi tock v polju 
     * tako ustrezajo indeksom ravnin.
	 *@param rv  polje ravnin
	 */
	public Tocka[] intersec(Ravnina rv[]) {
		pt = new Tocka[rv.length]; // vsebuje prebodno tocko vsake 
								   // ravnine ali null, ce ni preboda
		Tocka T[] = new Tocka[rv.length]; // vsebuje prebodne tocke,
								// ki lezijo znotraj poligona ravnine
								// ali null, ce lezijo zunaj
		Tocka tT = new Tocka();
		
		for(int i=0; i<rv.length; i++) {
		
			Vektor tr1,tr2;
			Matrix3D mat = new Matrix3D();
			double alfa,beta;
			double d = Math.sqrt(rv[i].n.y * rv[i].n.y + rv[i].n.z * rv[i].n.z);
			
    		if(d == 0) alfa = 0;
    		else { alfa = (rv[i].n.z >= 0) ? Math.asin(rv[i].n.y / d) * 180 / Math.PI :
                            	       180 - Math.asin(rv[i].n.y / d) * 180 / Math.PI;
    		}
      		beta = Math.asin(-rv[i].n.x) * 180 / Math.PI;

		 	// matrika za transformacijo ravnine rv[i] v ravnino xy:
			mat.unit();
			mat.translate(-rv[i].V.x,-rv[i].V.y,-rv[i].V.z);
			mat.xrot(-alfa);
			mat.yrot(beta);
			
		 	// transformacija premice:
			tr1 = mat.transform(r1);
			tr2 = mat.transform(r2);

			float Rz = tr2.z - tr1.z;

			if( Rz < -1.e-5f || 1.e-5f < Rz ) { // premica ni vzporedna ravnini
			 	
			 	// izracun tocke preboda ravnine xy:
				tT.x = tr1.x - tr1.z * (tr2.x - tr1.x) / Rz;
				tT.y = tr1.y - tr1.z * (tr2.y - tr1.y) / Rz;
				tT.z = 0.f;
	
			 	// inverzna transformacija (xy -> rv[i]):
				mat.unit();
				mat.yrot(-beta);
				mat.xrot(alfa);
				mat.translate(rv[i].V.x,rv[i].V.y,rv[i].V.z);
				
				pt[i] = mat.transform(tT);
				
			 	// vrne tocko preboda, ce lezi v poligonu:
				if(rv[i].inside(pt[i])) {
					T[i] = new Tocka(pt[i]);
				}
				else T[i] = null; // ne lezi v poligonu
			}
			// ni preboda:
			else { T[i] = null; pt[i] = null; }
		}
		return (T);
	}

	/**
	 * Izrise projekcijo premice na eno izmed koordinatnih ravnin. 
	 * Barva premice je podana s koordinatno ravnino.
	 *@param  g  graficni kontekst kamor naj izrise
	 *@param  i  indeks prve osi ravnine (x - 0, y - 1, z - 2)
	 *@param  j  indeks druge osi ravnine (x - 0, y - 1, z - 2)
	 *@param x0  x koordinata izhodisca na canvasu
	 *@param y0  y koordinata izhodisca na canvasu
	 */
	public void draw2D(Graphics g,int i,int j,int x0,int y0) {
		int k;
		if(i == 0 && j == 1) { // x in y
			// tocka preboda:
	       	if(XY != null) XY.draw2D(g,i,j,x0,y0,Color.red);
			// projekcija premice:		
			if(pt[0] != null && XY != null) k = 0;
			else if(pt[0] == null && XY != null) k = 1;
			else if(pt[0] == null && XY == null) k = 2;
			else k = 3; // (pt[0] != null && XY == null)
			
			switch(k) {
			case 0:
				if(pt[0].z > 0 && pt[3].z < 0) {
					full(g,i,j,x0,y0, pt[0], XY);
					dash(g,i,j,x0,y0, pt[3], XY);
            	}
				else {
					dash(g,i,j,x0,y0, pt[0], XY);
					full(g,i,j,x0,y0, pt[3], XY);
				}
				break;
			case 1:
				if(pt[1] == null) break;
				if(pt[1].z > 0 && pt[4].z < 0) {
					full(g,i,j,x0,y0, pt[1], XY);
					dash(g,i,j,x0,y0, pt[4], XY);
            	}
				else {
					dash(g,i,j,x0,y0, pt[1], XY);
					full(g,i,j,x0,y0, pt[4], XY);
				}
				break;
			case 2:
				if(pt[1] == null) break;
				if(pt[1].z >= 0 && pt[4].z >= 0) 
					 full(g,i,j,x0,y0, pt[1], pt[4]);
				else dash(g,i,j,x0,y0, pt[1], pt[4]);
				break;
			case 3:
				if(pt[0].z >= 0 && pt[3].z >= 0)
					full(g,i,j,x0,y0, pt[0], pt[3]);
				else if(pt[0].z <= 0 && pt[3].z <= 0) {
					dash(g,i,j,x0,y0, pt[0], pt[3]);
					break;
				    }
    			else if(pt[1].z >= 0 && pt[4].z >= 0)
    			    full(g,i,j,x0,y0, pt[1], pt[4]);
    			else dash(g,i,j,x0,y0, pt[1], pt[4]);
				break;
			default: System.out.println("mist!");
			}
		}
//		if(i == 0 && j == 2) { // x in z
		else {
			// tocka preboda:
	       	if(XZ != null) XZ.draw2D(g,i,j,x0,y0,Color.blue);
			// projekcija premice:		
			if(pt[0] != null && XZ != null) k = 0;
			else if(pt[0] == null && XZ != null) k = 1;
			else if(pt[0] == null && XZ == null) k = 2;
			else k = 3; // (pt[0] != null && XZ == null)
			
			switch(k) {
			case 0:
				if(pt[0].y > 0 && pt[3].y < 0) {
					full(g,i,j,x0,y0, pt[0], XZ);
					dash(g,i,j,x0,y0, pt[3], XZ);
            	}
				else {
					dash(g,i,j,x0,y0, pt[0], XZ);
					full(g,i,j,x0,y0, pt[3], XZ);
				}
				break;
			case 1:
				if(pt[2] == null) break;
				if(pt[2].y > 0 && pt[5].y < 0) {
					full(g,i,j,x0,y0, pt[2], XZ);
					dash(g,i,j,x0,y0, pt[5], XZ);
            	}
				else {
					dash(g,i,j,x0,y0, pt[2], XZ);
					full(g,i,j,x0,y0, pt[5], XZ);
				}
				break;
			case 2:
				if(pt[2] == null) break;
				if(pt[2].y >= 0 && pt[5].y >= 0) 
					 full(g,i,j,x0,y0, pt[2], pt[5]);
				else dash(g,i,j,x0,y0, pt[2], pt[5]);
				break;
			case 3:
				if(pt[0].y >= 0 && pt[3].y >= 0)
					full(g,i,j,x0,y0, pt[0], pt[3]);
				else if(pt[0].y <= 0 && pt[3].y <= 0) {
					dash(g,i,j,x0,y0, pt[0], pt[3]);
					break;
				    }
				else if(pt[2].y >= 0 && pt[5].y >= 0)
					full(g,i,j,x0,y0, pt[2], pt[5]);
			    else dash(g,i,j,x0,y0, pt[2], pt[5]);
				break;
			}
	    }
	}
	
	/** Med tockama narise polno crto. */
	private void full(Graphics g,int i,int j,int x0,int y0,Tocka t1,Tocka t2) {
		if(i == 0 && j == 1) { // x in y
			g.setColor(Color.red);
			g.drawLine(-(int)t1.x + x0,(int)t1.y + y0,
					   -(int)t2.x + x0,(int)t2.y + y0);
		}
//		if(i == 0 && j == 2) { // x in z
		else {
			g.setColor(Color.blue);
			g.drawLine(-(int)t1.x + x0,-(int)t1.z + y0,
					   -(int)t2.x + x0,-(int)t2.z + y0);
		}
	}

	/** Med tockama narise crtkano crto. */
	private void dash(Graphics g,int i,int j,int x0,int y0,Tocka t1,Tocka t2) {
		if(i == 0 && j == 1) { // x in y
			g.setColor(Color.red);
			float Rx = t2.x - t1.x;
			float Ry = t2.y - t1.y;
			double d = Math.sqrt((double)(Rx * Rx + Ry * Ry));
			// dolzina crtic in presledkov: 7 pikslov
			int n = (int)Math.ceil(d/7);
		   	
			int x[] = new int[n];
			int y[] = new int[n];

			for(int k=0; k<n; k++) {
				x[k] = (int)(t2.x - k * Rx / n);
				y[k] = (int)(t2.y - k * Ry / n);
			}
			for(int k=1; k<n-1; k+=2) {
				g.drawLine(-x[k] + x0,y[k] + y0,-x[k+1] + x0,y[k+1] + y0);
            }
		}
//		if(i == 0 && j == 2) { // x in z
		else {
			g.setColor(Color.blue);
			float Rx = t2.x - t1.x;
			float Rz = t2.z - t1.z;
			double d = Math.sqrt((double)(Rx * Rx + Rz * Rz));
			// dolzina crtic in presledkov: 7 pik
			int n = (int)Math.ceil(d/7);
		   	
			int x[] = new int[n];
			int z[] = new int[n];

			for(int k=0; k<n; k++) {
				x[k] = (int)(t2.x - k * Rx / n);
				z[k] = (int)(t2.z - k * Rz / n);
			}
			for(int k=1; k<n-1; k+=2) {
				g.drawLine(-x[k] + x0,-z[k] + y0,-x[k+1] + x0,-z[k+1] + y0);
            }
		}
	}
	
	/**
	 * Izrise premico v 3D prostoru med tockama A in B.
	 *@param g  graficni kontekst kamor naj izrise
	 *@param x0  x koordinata izhodisca na canvasu
	 *@param y0  y koordinata izhodisca na canvasu
	 *@param mat  matrika 3D transformacij
	 *@param c  barva s katero naj izrise
	 */
	public void draw3D(Graphics g,int x0,int y0,Matrix3D mat,Color c) {
		if(A != null && B != null) {	
			g.setColor(c);
			Tocka tA = mat.transform(A);	
			Tocka tB = mat.transform(B);	
			    
		    g.drawLine((int)tA.x + x0,-(int)tA.y + y0,
		    		   (int)tB.x + x0,-(int)tB.y + y0);
		    		   
/*			// rise vse prebodne tocke ravnin mejnega kvadra:
			for(int i=0; i<pt.length; i++) {
	    	   	if(pt[i] != null) pt[i].draw3D(g,x0,y0,mat,Color.yellow);
			}
*/
			// rise prebodne tocke koordinatnih ravnin:
	      	if(XZ != null) XZ.draw3D(g,x0,y0,mat,Color.blue);
	       	if(XY != null) XY.draw3D(g,x0,y0,mat,Color.red);
	       	if(YZ != null) YZ.draw3D(g,x0,y0,mat,Color.green);
		}
		else { // ce gre premica mimo kvadra, narise "neskoncno" premico:
			g.setColor(c);

			A = new Tocka(r1.x - 100 * x0 * nR.x, 
								r1.y - 100 * x0 * nR.y,
								r1.z - 100 * x0 * nR.z);
			B = new Tocka(r2.x + 100 * x0 * nR.x,
								r2.y + 100 * x0 * nR.y,
								r2.z + 100 * x0 * nR.z);
			
			Tocka tA = mat.transform(A);	
			Tocka tB = mat.transform(B);	
			    
		    g.drawLine((int)tA.x + x0,-(int)tA.y + y0,
		    		   (int)tB.x + x0,-(int)tB.y + y0);
        }
    }
}
