import java.awt.*;
import java.util.Random;
public class BuggyMonolith extends MISApplet {
	static Random rnd_ = new Random();
	double F = 10.,lastt_=0.,EPS=0.001;
	PointyPoint lightSources_[] = new PointyPoint[1]; // overload pt=dir, v=rgb
	Vector3D ambient_ = new Vector3D(.1,.1,.1);
	RTShape shapes_[] = new RTShape[10];
	class RayState {
		// I am so hating not having real stack allocation right now!
		Vector3D color = new Vector3D(false), 
			v = new Vector3D(true), 
			w = new Vector3D(false),
			hitPoint = new Vector3D(true),
			normal = new Vector3D(false);
	}
	final static int RDEPTH = 4;
	RayState rayStack_[] = new RayState[RDEPTH];
	RTSphere s1,s2,s3;
	RTPolyhedron cube;
	Matrix3D cubeXlator_ = new Matrix3D(),
		cubeXlatorInverse_ = new Matrix3D(),
		matrixHell_ = new Matrix3D();
	public BuggyMonolith() {
		double sqrt3 = Math.sqrt(3.);
		for(int i=0; i<RDEPTH; ++i)
			rayStack_[i] = new RayState();
		lightSources_[0] = new PointyPoint(new Vector3D(50,25,50),new Vector3D(1,1,1));
		lightSources_[0].pt.normalize();
		s1 = new RTSphere();
		s1.center_.set(0,.75);
		s1.center_.set(1,.6);
		s1.center_.set(2,-25.);
		s1.r_ = 2;
		s1.color_.set(0,.5);
		s1.color_.set(1,.4);
		s1.color_.set(2,.1);
		s1.shiny_ = 10;
		s1.mattePortion_ = .5;
		s1.reflectPortion_ = .5;
		shapes_[0] = s1;
		s2 = new RTSphere();
		s2.r_ = .5;
		s2.color_.set(0,.7);
		s2.color_.set(1,0);
		s2.color_.set(2,.3);
		s2.color_.times(.7,s2.glare_);
		s2.shiny_ = 5;
		s2.mattePortion_ = .3;
		s2.reflectPortion_ = .2;
		s2.refractPortion_ = .8;
		s2.refractionIndex_ = 1.75;
		shapes_[1] = s2;	
		s3 = new RTSphere();
		s3.r_ = .3;
		s3.color_.set(0,.2);
		s3.color_.set(1,0);
		s3.color_.set(2,.8);
		s3.color_.times(.7,s3.glare_);
		s3.shiny_ = 5;
		s3.mattePortion_ = .6;
		s3.reflectPortion_ = .4;
		shapes_[2] = s3;
		
		cube = new RTPolyhedron(6);
		cube.planes_[0].set(0,-1);
		cube.planes_[1].set(0,+1);
		cube.planes_[2].set(1,-1);
		cube.planes_[3].set(1,+1);
		cube.planes_[4].set(2,-1);
		cube.planes_[5].set(2,+1);
		cube.color_.set(0,.05);
		cube.color_.set(1,.05);
		cube.color_.set(2,.05);
		cube.mattePortion_ = cube.reflectPortion_ = 0.5;
		shapes_[3] = cube;
		
	}
	static Vector3D tmp_=new Vector3D(),tmp2_=new Vector3D();
	static final double MSPEED = 0.23, MMSPEED = 0.37, CSPEED=0.1;
	double speedMult_ = .5, rott_=0;
    public void initFrame(double time) { 
		double dt = time - lastt_;
		System.out.println("frame dt "+dt);
		lastt_ = time;
		rott_ += dt*speedMult_;
		tmp_.set(0,3.5*Math.cos(rott_*MSPEED));
		tmp_.set(1,.9*Math.cos(rott_*MSPEED));
		tmp_.set(2,3.5*Math.sin(rott_*MSPEED));
		s1.center_.add(tmp_,s2.center_);
		tmp_.set(0,.8*Math.cos(rott_*MMSPEED));
		tmp_.set(1,.8*Math.cos(rott_*MMSPEED));
		tmp_.set(2,.8*Math.sin(rott_*MMSPEED));
		s2.center_.add(tmp_,s3.center_);

		cubeXlator_.scaler(.55,.21,.34);
		cubeXlator_.rotateX(rott_*CSPEED);
		cubeXlator_.rotateY(rott_*CSPEED);
		cubeXlator_.rotateZ(rott_*CSPEED);
		
		tmp_.set(0,-3.5*Math.cos(rott_*MSPEED));
		tmp_.set(1,-.9*Math.cos(rott_*MSPEED));
		tmp_.set(2,-3.5*Math.sin(rott_*MSPEED));
		s1.center_.add(tmp_,tmp2_);
		cubeXlator_.translate(tmp2_);
		cubeXlator_.inverse(cubeXlatorInverse_);
		cube.transform(cubeXlatorInverse_);
    }
    public void setPixel(int x, int y, int rgb[]) { 
		RayState state = rayStack_[0];
		state.w.set(0,(double)(x - W/2) / (W/2));
		state.w.set(1,(double)(H/2 - y) / (W/2));
		state.w.set(2,-F);
		state.w.normalize();
		rayTrace(0);
		rgb[0] = (int)(state.color.get(0)*255.);
		rgb[1] = (int)(state.color.get(1)*255.);
		rgb[2] = (int)(state.color.get(2)*255.);		
    }
	static Vector3D colorPart_ = new Vector3D(),
		temp_ = new Vector3D(),
		temp2_ = new Vector3D(),
		wprime_ = new Vector3D(),
		refl_ = new Vector3D();
	public void rayTrace(int depth) {
		RayState state = rayStack_[depth];
		// find the closest
		double t = 1e40;
		RTShape closest = null;
		for(RTShape sh : shapes_)
			if(sh!=null) {
				double ts = sh.rayHits(state.v,state.w,temp_,temp2_);
				if(ts>0 && ts<t) {
					closest = sh;
					t = ts;
					state.hitPoint.copy(temp_);
					state.normal.copy(temp2_);
				}
			}
		if(closest==null) {
			state.color.zero();
			return;
		}

		// ambient
		closest.color_.times(ambient_,state.color);
		
		sources: 
		for(PointyPoint source : lightSources_) {
			wprime_.copy(source.pt);
			for(RTShape sh : shapes_)
				if(sh!=null && sh!=closest) 
					if(sh.rayHits(state.hitPoint,wprime_,temp_,temp2_)>0)
						continue sources; // shadow	
			if(source!=null) {
				double ndotl = state.normal.dot(source.pt);
				if(ndotl<EPS)
					continue;
					
				// diffuse
				closest.color_.times(source.v,colorPart_);
				colorPart_.times(ndotl);
				state.color.add(colorPart_);
				
				// specular
				state.normal.times(2*ndotl,refl_);
				refl_.subtract(source.pt);
				closest.glare_.times(source.v,colorPart_);
				temp_.copy(state.w);
				temp_.reverse();
				double wdotrefl=temp_.dot(refl_); 
				if(wdotrefl>0) {
					colorPart_.times(Math.pow(wdotrefl,closest.shiny_));
					state.color.add(colorPart_);
				}
			}
		}
		if(depth==RDEPTH-1 
			|| closest.reflectPortion_<EPS && closest.refractPortion_<EPS)
			return;
		RayState next = rayStack_[depth+1];
		state.color.times(closest.mattePortion_);
		double wdotn = state.w.dot(state.normal);
		// reflection
		if(closest.reflectPortion_>EPS) {
			state.normal.times(-2*wdotn,temp_);
			state.w.add(temp_,next.w);
			next.w.times(EPS,temp_);
			state.hitPoint.add(temp_,next.v);
			rayTrace(depth+1);
			next.color.times(closest.reflectPortion_);
			state.color.add(next.color);
		}
		// refraction doesn't (fucking) work
		if(closest.refractPortion_>EPS) {
			state.normal.times(wdotn,temp_); // perpendicular component
			state.normal.subtract(temp_,next.w); // parallel component
			temp_.times(closest.refractionIndex_);
			next.w.add(temp_);
			next.w.normalize();
			next.w.reverse();
			next.w.times(EPS,temp_);
			state.hitPoint.add(temp_,next.v);
			rayTrace(depth+1);
			next.color.times(closest.refractPortion_);
			state.color.add(next.color);
		}
	}
    public boolean keyDown(Event e, int key) {
        switch(key) {
        case Event.UP:
			speedMult_ *= 1.25;
            break;
        case Event.DOWN:
			speedMult_ /= 1.25;
            break;
        case Event.RIGHT:
			rott_ += 1.;
            break;
        case Event.LEFT:
			rott_ -= 1.;
            break;
        }           
        return true;
    }   
}
