import java.awt.*;
import java.util.Random;
public class RefractMobile extends MISApplet {
    static Random rnd_ = new Random();
    double F = 10.,lastt_=0.,EPS=0.001;
    Vertex lightSources_[] = new Vertex[1]; // overload pt=dir, v=rgb
    Vector3D ambient_ = new Vector3D(.1,.1,.1);
    Matrix3D view_ = new Matrix3D();
    RTShape shapes_[] = new RTShape[512];
    int nshapes_ = 0;
    Vector3D mouse_ = new Vector3D();
    boolean pushing_;
    int depth_;
    Spinny spinny_ = new Spinny(0);
    Rotator spinnyRots_ = new Rotator(spinny_);
    Translator spinnyXlat_ = new Translator(spinnyRots_);
    RTCube cube_ = new RTCube();
    Rotator cubeRotator_ = new Rotator(cube_);
    class RayState {
        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];
    public RefractMobile() {
        for(int i=0; i<RDEPTH; ++i)
            rayStack_[i] = new RayState();
        lightSources_[0] = new Vertex(new Vector3D(50,25,50),new Vector3D(1,1,1));
        lightSources_[0].pt.normalize();
        //shapes_[0] = cube_;
        //nshapes_ = 1;
        //cube_.matteColor_.set(0.5,0.5,0.5);
        nshapes_ = spinny_.register(shapes_,0);
        view_.translator(0,0,-16);
        spinnyRots_.change(tmp_.set(0,0,-Math.PI/2));
        spinnyXlat_.change(tmp_.set(0,-1,0));
        spinny_.expand(2,true);
    }
    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+" nsh "+nshapes_+" fps "+1/dt);
        //System.out.println("view "+view_);
        lastt_ = time;
        spinny_.go(time,dt);
        //cubeRotator_.change(tmp_.set(time/15,time/15,time/15));
        //cubeRotator_.transform(view_);
        //spinnyRots_.change(tmp_.set(time/20,time/20,time/20));
        spinnyXlat_.transform(view_);
    }
    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(int i = 0; i<nshapes_; ++i) {
            RTShape sh = shapes_[i];
            if(sh!=null) {
                double ts = sh.rayHits(state.v,state.w);
                if(ts>0 && ts<t) {
                    closest = sh;
                    t = ts;
                }
            }
        }
        if(closest==null) {
            double shade = Math.max(0.25,Math.min(.6,state.w.y()*3 + 0.5));
            state.color.set(shade,shade,shade,0);
            return;
        }
        closest.getNormal(t,state.v,state.w,state.hitPoint,state.normal);
        // System.out.println("hit "+state.hitPoint);
        // ambient
        closest.matteColor_.times(ambient_,state.color);
        
        sources: 
        for(Vertex source : lightSources_) {
            if(source!=null) {
                wprime_.copy(source.pt);
                for(RTShape sh : shapes_)
                    if(sh!=null && sh!=closest) 
                        if(sh.rayHits(state.hitPoint,wprime_)>0)
                            continue sources; // shadow 
                double ndotl = state.normal.dot(source.pt);
                if(ndotl<EPS)
                    continue;
                    
                // diffuse
                closest.matteColor_.times(source.v,colorPart_);
                colorPart_.times(ndotl);
                state.color.add(colorPart_);
                
                // specular
                state.normal.times(2*ndotl,refl_);
                refl_.minus(source.pt);
                closest.glareColor_.times(source.v,colorPart_);
                temp_.copy(state.w);
                temp_.reverse();
                double wdotrefl=temp_.dot(refl_); 
                if(wdotrefl>0) {
                    colorPart_.times(Math.pow(wdotrefl,closest.shinyExp_));
                    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 
        if(closest.refractPortion_>EPS) {
            state.normal.times(wdotn,temp_); // perpendicular component
            state.normal.minus(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_);
            next.color.times(closest.transpColor_,temp_);
            state.color.add(temp_);
        }
    }
    public boolean mouseMove(Event e, int wx, int wy) {
        mouse_.set(0,wx);
        mouse_.set(1,wy);
        return true;
    }
    public boolean mouseDown(Event e, int wx, int wy) {
        pushing_ = true;
        return true;
    }
    public boolean mouseUp(Event e, int wx, int wy) {
        pushing_ = false;
        return true;
    }
    public boolean keyDown(Event e, int key) {
        switch(key) {
        case Event.UP:
            depth_ = Math.min(6,depth_+2);
            spinny_.expand(depth_);
            nshapes_ = spinny_.register(shapes_,0);
            break;
        case Event.DOWN:
            depth_ = Math.max(0,depth_-2);
            spinny_.contract(depth_);
            nshapes_ = spinny_.register(shapes_,0);
            break;
        case 'z':
            spinny_.zounds();
            break;
        case Event.RIGHT:
            Spinny.DRAG -= 0.01;
            break;
        case Event.LEFT:
            Spinny.DRAG = Math.min(Spinny.DRAG+0.01,1.);
            break;
        }           
        return true;
    }   
}
