/**
   La classe permette di definire parallelepipedi. Un parallelepipedo e' 
   definito da 8 vertici, 12 lati e 6 facce. 
   Di piu' ad ogni faccia e' associato un vettore ad essa ortogonale
   e con verso uscente dalla faccia. Tale vettore serve per determinare
   se la faccia e' visibile o no (se il vettore punta verso l'osservatore
   la faccia e' visibile, altrimenti non lo e').
*/

public class Parallelepipedo {

    // gli 8 vertici (ognuno contiene le 3 coordinate reali del cubo)
    private double[][] vertice = new double[8][3];

    // ognuno dei 12 lati e' fatto dal segmento congiungente 2 
    // vertici:
    private int[][] lato = new int[12][2];

    // ognuna delle 6 facce e' data dai 4 vertici che la delimitano
    private int[][] faccia = new int[6][4];

    // numero vertici del parallelepipedo:
    private int numVertici = 8;

    /**
       Costruttore
       @param w l'array con le coordinate spaziali degli 8 vertici del cubo
       @param l l'array con le coppie di punti che costituiscono i lati
       @param f l'array con le quaterne di punti che costituiscono le facce.
    */
    public Parallelepipedo(double[][] w, int[][] l, int[][] f) {
	for (int i = 0; i < 8; i++) {
	    for (int j = 0; j < 3; j++) {
		vertice[i][j] = w[i][j];
	    }
	}
	for (int i = 0; i < 12; i++) {
	    for (int j = 0; j < 2; j++) {
		lato[i][j] = l[i][j];
	    }
	}
	for (int i = 0; i < 6; i++) {
	    for (int j = 0; j < 4; j++) {
		faccia[i][j] = f[i][j];
	    }
	}
    }

    /**
       Restituisce un vettore uscente da una faccia del cubo. 
       Si richiede che i primi tre vertici V1, V2, V3 della faccia
       sono messi in modo opportuno, ossia siano tali il prodotto
       vettoriale di V2 - V1 e V3 - V1 sia un vettore orientato
       verso l'esterno del parallelepipedo.
       @fc la faccia di cui si vuole avere il vettore uscente.
    */
    public double[] vettoreNormaleFaccia(int fc) {
	double[] v1, v2;

	v1 = diffVettori(vertice[faccia[fc][1]], vertice[faccia[fc][0]]);
	v2 = diffVettori(vertice[faccia[fc][2]], vertice[faccia[fc][1]]);

	return new double[] {v1[1]*v2[2]-v1[2]*v2[1],
	                     v1[2]*v2[0]-v1[0]*v2[2],
                             v1[0]*v2[1]-v1[1]*v2[0]};
    }

    private double[] diffVettori(double[] a, double[] b) {
	return new double[] {a[0]-b[0], a[1]-b[1], a[2]-b[2]};
    }


    /**
       Trasla il parallelepipedo della quantita' assegnata
       @param a traslaz. lungo asse x
       @param b traslaz. lungo asse y
       @param c tralsaz. lungo asse z
     */
    public void traslaSolido(double a, double b, double c) {
	for (int i = 0; i < numVertici; i++) {
	    vertice[i][0] += a;
	    vertice[i][1] += b;
	    vertice[i][2] += c;
	}
    }

    private double[] ruotaPunto(double[] retta, double[] punto, double ang) {
	// retta[] e' una terna di punti che, congiunti con l'origine
	// danno appunto una retta. Questo metodo ruota il punto in senso
	// orario di un angolo ang attorno alla retta. L'output e' il 
	// punto ruotato.

	double a = retta[0];
	double b = retta[1];
	double c = retta[2];
	double r = a*a + b*b + c*c;

	double x = 
	    (a*a+Math.cos(ang)*b*b+Math.cos(ang)*c*c)/r*punto[0]
	    -(-a*b+a*Math.cos(ang)*b-c*Math.sin(ang)*Math.sqrt(r))/r*punto[1]
	    -(-a*c+a*Math.cos(ang)*c+b*Math.sin(ang)*Math.sqrt(r))/r*punto[2];

	double y = 
	    -(-a*b+a*Math.cos(ang)*b+c*Math.sin(ang)*Math.sqrt(r))/r*punto[0]
	    +(Math.cos(ang)*a*a+b*b+Math.cos(ang)*c*c)/r*punto[1]
	    +(a*Math.sin(ang)*Math.sqrt(r)-c*b*Math.cos(ang)+b*c)/r*punto[2];

	double z = 
	    (c*a*Math.sqrt(r)-c*a*Math.sqrt(r)*Math.cos(ang)
	    +Math.sin(ang)*b*a*a+Math.sin(ang)*b*b*b
	    +Math.sin(ang)*b*c*c)/(r*Math.sqrt(r))*punto[0]
	    -(-c*b*Math.sqrt(r)+c*b*Math.sqrt(r)*Math.cos(ang)
            +a*a*a*Math.sin(ang)+a*Math.sin(ang)*b*b
            +a*Math.sin(ang)*c*c)/(r*Math.sqrt(r))*punto[1]
            +(c*c+Math.cos(ang)*a*a+Math.cos(ang)*b*b)/r*punto[2];

	return new double[] {x, y, z};
	
    }

    /**
       Ruota il solido di un dato angolo attorno ad una retta passante 
       per 0. 
       @retta la retta attorno a cui ruotare (data da una terna di
       numeri che, assieme all'origine, da' la retta).
       @angolo angolo della ruotazione.
       
    */

    public void ruotaSolido(double[] retta, double angolo) {
	double[] aux = new double[3];

	for (int i = 0; i < numVertici; i++) {
	    aux = ruotaPunto(retta, vertice[i], angolo);
	    vertice[i][0] = aux[0];
	    vertice[i][1] = aux[1];
	    vertice[i][2] = aux[2];
	}
	
    }

    /**
       Ritorna i vertici del parallelepipedo
       @return array 8x3.
     */
    public double[][] getVertici() {
	return vertice;
    }

    /**
       Ritorna i vertici della faccia i-ima
       @param i l'i-ima faccia
    */

    public int[] getVerticiFaccia(int i) {
	return faccia[i];
    }


    /**
       Dice se una faccia e' visibile (rispetto ad un vettore direzione).
       @param i numero della faccia.
       @param direzOss la direzione di osservazione.
    */
    public boolean facciaVisibile(int i, double[] direzOss) {

	double[] aux = vettoreNormaleFaccia(i);


	// se il vettore uscente dalla faccia e' nello stesso verso
	// del vettore osservazione, quindi se il loro prodotto scalare
	// e' positivo, la faccia e' visibile.

	return ((aux[0] * direzOss[0] +
		 aux[1] * direzOss[1] +
		 aux[2] * direzOss[2]) > 0);

    }

}
