public class SpheresApplet extends PixApplet {

    public double focalLength = 3;

    public final static int X = 0;
    public final static int Y = 1;
    public final static int Z = 2;
    public final static int Rad = 3;
    public final static int RED = 4;
    public final static int GREEN = 5;
    public final static int BLUE = 6;

    double[][] spheres = {
        {1.1,0.0,-15.0,1.0, 0.9,0.5,0.5},
        {-1.1,1.1,-15.0,1.0, 0.4,0.9,0.4},
        {-1.1,-1.1,-15.0,1.0, 0.4,0.4,0.9},
    };

    // trace a ray
    public void rayTrace(double[] v, double[] w, double[] rgb){
        
        double t = 100000.0;  // <--- infinity :-)
        double[] obj = null;
        double[] s = null;
        double t1,t2;

        double[] tmp = {0,0,0};        
        for(int i=0;i<spheres.length;i++){
            s = spheres[i];
            double A = dotProduct(w,w);
            vectorSubtract(v,s,tmp);
            double B = 2.0 * dotProduct(tmp,w);
            double C = dotProduct(tmp,tmp) - s[Rad]*s[Rad];
            
            // did we miss the sphere?
            double b24ac = B*B - 4.0*A*C;
            
            if(b24ac <= 0)
                continue;
                
            double sq_b24ac = Math.sqrt(b24ac);
            t1 = (-B - sq_b24ac)/(2.0*A);
            t2 = (-B + sq_b24ac)/(2.0*A);
            t1 = Math.min(t1,t2);

            // is sphere in front of the ray?
            if(t1 > 0 && t1 < t){
                t = t1;
                obj = s;
            }
        }
        if(obj == null){
            rgb[0] = 0.8; // 0.5*Math.cos(w[0]*Math.PI/2.0);
            rgb[1] = 0.8; // 0.5*Math.cos(w[1]*Math.PI/2.0);
            rgb[2] = 0.8; // 0.5*Math.cos(w[2]*Math.PI/2.0);
            return;
        }
        
        //System.out.println("t: "+t);

        // Compute surface point S = v + tw
        double[] S = {
            v[0]+t*w[0],
            v[1]+t*w[1],
            v[2]+t*w[2],
        };
            
        // Compute surface normal n = (S-[cx,cy,cz])/r
        double[] n = {
//            (S[X]-obj[X])/obj[Rad],
//            (S[Y]-obj[Y])/obj[Rad],
//            (S[Z]-obj[Z])/obj[Rad],

            (obj[X]-S[X])/obj[Rad],
            (obj[Y]-S[Y])/obj[Rad],
            (obj[Z]-S[Z])/obj[Rad],
        };
       
        vectorNormalize(n,n);

        
        // Compute reflection vector R = -2(w * n)n + w
        double wn = dotProduct(w,n);
        double[] R = {
            -2.0 * wn * n[X] + w[X],
            -2.0 * wn * n[Y] + w[Y],
            -2.0 * wn * n[Z] + w[Z],
        };
        vectorNormalize(R,R);

        // Create reflected ray with v = (S+eR) and w = R.
        double[] Rv = {
            S[X] + 0.2 * R[X],
            S[Y] + 0.2 * R[Y],
            S[Z] + 0.2 * R[Z],
        };
        
        double[] newrgb = new double[3];
        rayTrace(Rv,R,newrgb);
                
//        rgb[0] = (0.15 * (1.0 - obj[RED])) + ((0.85) * rgb[0]) * wn;
//        rgb[1] = (0.15 * (1.0 - obj[GREEN])) + ((0.85) * rgb[1]) * wn;        
//        rgb[2] = (0.15 * (1.0 - obj[BLUE])) + ((0.85) * rgb[2]) * wn;

        rgb[0] = (newrgb[0]*wn*0.9*obj[RED]);
        rgb[1] = (newrgb[1]*wn*0.9*obj[GREEN]);
        rgb[2] = (newrgb[2]*wn*0.9*obj[BLUE]);


        /*
        rgb[0] = rgb[0] * obj[RED];        
        rgb[1] = rgb[1] * obj[GREEN];        
        rgb[2] = rgb[2] * obj[BLUE];
        */
    }

    /**
     * SET PIXELS FOR THIS ANIMATION FRAME
     * (THIS OVERRIDES A METHOD IN PIXAPPLET CLASS)
     */
    public void setPix(int frame) { 
    
    
        double[] rgb = new double[]{0,0,0};
        double[] v = new double[]{0,0,0};
        double[] w = new double[]{0,0,0};
    
        v[0] = 0;
        v[1] = 0; // CAMERA EYEPOINT IS AT THE ORIGIN
        v[2] = 0;

        int n = 0;
        for (int j = 0 ; j < H ; j++)   // LOOP OVER IMAGE ROWS
            for (int i = 0 ; i < W ; i++) { // LOOP OVER IMAGE COLUMNS

                w[0] = (double)(i - W/2) / W;     // COMPUTE RAY DIRECTION AT EACH PIXEL
                w[1] = (double)(H/2 - j) / W;     //
                w[2] = -focalLength;              // PLACE IMAGE PLANE AT z = -focalLength

                vectorNormalize(w,w);

                rayTrace(v, w, rgb);              // RAY TRACE AT THIS PIXEL

	            pix[n++] = pack(
                    (int)(255 * rgb[0]), 
                    (int)(255 * rgb[1]), 
                    (int)(255 * rgb[2]));
            }
    }
            
    
	/**
	 * given 2 points, find their difference
	 */
	void vectorSubtract (double[] va, double[] vb, double[] out){
		out[0] = va[0]-vb[0];
		out[1] = va[1]-vb[1];
		out[2] = va[2]-vb[2];
	}
	
	/**
	 * cross product of two vectors
	 */
	void crossProduct(double[] v1, double[] v2, double[] cross ) {
		cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
		cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
		cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
	}

	/**
	 * normalize
	 */
	double vectorNormalize(double[] in, double[] out ) {
		double	length, ilength;
		length = Math.sqrt(in[0]*in[0] + in[1]*in[1] + in[2]*in[2]);
		if (length == 0){
			return 0;
		}
		ilength = 1.0/length;
		out[0] = in[0]*ilength;
		out[1] = in[1]*ilength;
		out[2] = in[2]*ilength;
		return length;
	}	

	/**
	 * dot product
	 */
	double dotProduct (double[] v1, double[] v2){
		return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
	}
    
    public boolean keyUp(java.awt.Event e,int key){
        System.out.println("key: "+key);
        
        
        System.out.println("spheres("+spheres[0][0]+","+spheres[0][1]+","+spheres[0][2]+"): "+spheres[0][3]+"; (focal: "+focalLength+")\n");
        
        
        
        if(key == 'a')
            spheres[0][2]-=0.1;
        if(key == 'z')
            spheres[0][2]+=0.1;

        if(key == 'f')
            focalLength-=0.1;
        if(key == 'v')
            focalLength+=0.1;

        damage = true;

        
        return true;
    }    
    
    
}


