import java.applet.*;
import java.awt.*;
import go.*;

class Function extends GoInterface
{
    //
    // The Function.
    //
    double fxy(double x, double y)
    {
        return 1.0 - Math.cos(x*x + y*y) / (x*x + y*y);
    }


    // View variables.
    double  eyeX, eyeY, eyeZ;
    double  centerX, centerY, centerZ;
    double  upX, upY, upZ;
    double  radius;
    boolean perspective;

    // Function data
    int triangleStripNum;
    int lineStripNum;
    GoVertex[] triangleStripData;
    GoVertex[] lineStripData;
    double[] normal = new double[3];
    double zClampMin, zClampMax;
    int drawMode;

    // Box data
    GoVertex top, bottom, sides;

    // Rotate vector.
    double rX, rY, rZ;
    int rotateAbout;
    int rotateAs;

    // Values for rotateAbout
    final static int X_AXIS = 1;
    final static int Y_AXIS = 2;
    final static int Z_AXIS = 3;
    
    // Values for rotateAs
    final static int SOLID = 0;
    final static int WIRE  = 1;
    final static int BOX   = 2;

    // Class temporary variables
    GoMatrix modelview = new GoMatrix();
    GoMatrix saveModelview = new GoMatrix();        
    int xStart, yStart;
    double angleStart;


    //
    // Constructor.
    //
    Function(double xMin, double xMax,
             double yMin, double yMax,
             double zMin, double zMax,
             int xDiv, int yDiv,
             double eyeX, double eyeY, double eyeZ,
             double upX, double upY, double upZ,
             boolean perspective)
    {
        //
        // Create the function and bounding box.
        //

        createFunction(xMin, xMax, yMin, yMax, zMin, zMax, xDiv, yDiv);
        createBox(xMin, yMin, zMin, xMax, yMax, zMax);

        //
        // Default drawing mode.
        //

        drawMode = SOLID;
        rotateAbout = Y_AXIS;
        rotateAs = WIRE;

        //
        // Setup a light.
        //
        
        go.light(Go.LIGHT_0, true);
        go.light(Go.LIGHT_0, Go.DIRECTIONAL, -1.3, 2.4, 1.0);
        
        //
        // Define the initial view.
        // NOTE: View variables are used in the resized() method.
        //
        
        this.eyeX  = eyeX;
        this.eyeY  = eyeY;
        this.eyeZ  = eyeZ;
        centerX    = (xMin + xMax) / 2.0;
        centerY    = (yMin + yMax) / 2.0;
        centerZ    = (zMin + zMax) / 2.0;
        this.upX   = upX;
        this.upY   = upY;
        this.upZ   = upZ;
        radius     = distance(centerX, centerY, centerZ,
                              xMax, yMax, zMax);
        this.perspective = perspective;
        
        //
        // Initialize the modelview matrix.
        //
        
        go.lookAt(eyeX, eyeY, eyeZ,
                  centerX, centerY, centerZ,
                  upX, upY, upZ);
    }

    //
    // Interface render method.
    //
    public void render()
    {
        int i;

        go.clear(Go.IMAGE);

        //
        // Render function.
        //

        switch(drawMode) {

        case SOLID:
            go.color(1, 1, 1);  // white
            for(i = 0; i < triangleStripNum; i++) {
                go.render(triangleStripData[i]);
            }
            
            go.color(1, 0, 0);  // red
            for(i = 0; i < lineStripNum; i++) {
                go.render(lineStripData[i]);
            }
            break;

        case WIRE:
            go.color(1, 0, 0);  // red
            for(i = 0; i < lineStripNum; i++) {
                go.render(lineStripData[i]);
            }
            break;
        }

        //
        // Render box.
        //

        go.color(0, 0, 0);  // black
        go.render(top);
        go.render(bottom);
        go.render(sides);

        swap();
    }

    //
    // Interface resized method.
    //
    public void resized(int width, int height)
    {
        double dx = eyeX - centerX;
        double dy = eyeY - centerY;
        double dz = eyeZ - centerZ;
        double d = Math.sqrt(dx * dx + dy * dy + dz * dz);

        double fovy = 2.0 * 180.0/Math.PI * Math.atan(radius / d);
        double near = d - radius;
        double far  = d + radius;

        double xUnits, yUnits;

        if(width >= height) {
            xUnits = 2 * radius * width / height;
            yUnits = 2 * radius;
        }
        else {
            xUnits = 2 * radius;
            yUnits = 2 * radius * height / width;
        }

        // setup projection matrix
        go.matrixMode(Go.PROJECTION);
        go.identity();
        if(perspective) {
            if(width >= height) {
                go.perspective(fovy, xUnits / yUnits, near, far);
            }
            else {
                double radius2 = radius * yUnits / xUnits;
                double fovyModified = Math.atan(radius2 / d) * 180.0 / Math.PI * 2.0;
                go.perspective(fovyModified, xUnits / yUnits, near, far);
            }
        }
        else {
            go.ortho(-xUnits / 2.0, xUnits / 2.0,
                     -yUnits / 2.0, yUnits / 2.0,
                     near, far);
        }
        go.matrixMode(Go.MODELVIEW);
    }

    //
    // Interface keyTyped method.
    //
    public void keyTyped(char key)
    {
        if(key == 's') {
            rotateAs = SOLID;
        }
        else if(key == 'w') {
            rotateAs = WIRE;
        }
        else if(key == 'b') {
            rotateAs = BOX;
        }
        else if(key == '1') {
            rotateAbout = X_AXIS;
        }
        else if(key == '2') {
            rotateAbout = Y_AXIS;
        }
        else if(key == '3') {
            rotateAbout = Z_AXIS;
        }
    }
    
    //
    // Interface mousePressed method.
    //
    public void mousePressed(int x, int y)
    {
        requestFocus();

        int width = go.width();
        int height = go.height();

        //
        // Init mouse info.
        // 
                        
        xStart = x;
        yStart = y;
        angleStart = Math.atan2(height / 2.0 - yStart,
                                xStart - width / 2.0);

        //
        // Save the current modelview matrix so that
        // it can be restored as the mouse is moved.
        //

        go.getModelview(saveModelview);
                    
        //
        // Redisplay in rotateAs mode.
        //

        switch(rotateAs) {
                
        case SOLID:
            drawMode = SOLID;
            break;

        case WIRE:
            drawMode = WIRE;
            break;

        case BOX:
            drawMode = BOX;
            break;
        }

        rerender();

        //
        // Calc the rotation vector <rX, rY, rZ>
        //

        double x0 = 0.0;
        double y0 = 0.0;
        double z0 = 0.0;
        double x1;
        double y1;
        double z1;

        switch(rotateAbout) {

        case X_AXIS:
            x1 = 1.0;
            y1 = 0.0;
            z1 = 0.0;
            break;

        default:
        case Y_AXIS:
            x1 = 0.0;
            y1 = 1.0;
            z1 = 0.0;
            break;

        case Z_AXIS:
            x1 = 0.0;
            y1 = 0.0;
            z1 = 1.0;
            break;
        }

        go.push(Go.MODELVIEW);
        go.inverse();
        go.getModelview(modelview);
        go.pop(Go.MODELVIEW);

        double[] m = modelview.m;

        double w = m[3] * x0 + m[7] * y0 + m[11] * z0 + m[15];
        double x0N = (m[0] * x0 + m[4] * y0 + m[8]  * z0 + m[12]) / w;
        double y0N = (m[1] * x0 + m[5] * y0 + m[9]  * z0 + m[13]) / w;
        double z0N = (m[2] * x0 + m[6] * y0 + m[10] * z0 + m[14]) / w;

        w = m[3] * x1 + m[7] * y1 + m[11] * z1 + m[15];
        double x1N = (m[0] * x1 + m[4] * y1 + m[8]  * z1 + m[12]) / w;
        double y1N = (m[1] * x1 + m[5] * y1 + m[9]  * z1 + m[13]) / w;
        double z1N = (m[2] * x1 + m[6] * y1 + m[10] * z1 + m[14]) / w;

        rX = x1N - x0N;
        rY = y1N - y0N;
        rZ = z1N - z0N;
    }

    //
    // Interface mouseReleased method.
    //
    public void mouseReleased(int x, int y)
    {
        drawMode = SOLID;
        rerender();
    }

    //
    // Interface mouseDragged method.
    //
    public void mouseDragged(int x, int y)
    {
        int xEnd = x;
        int yEnd = y;
        
        int width = go.width();
        int height = go.height();
        
        go.load(saveModelview);

        double delta;

        switch(rotateAbout) {

        case X_AXIS:
            delta = (yEnd - yStart) / 2.0;
            break;

        default:
        case Y_AXIS:
            delta = (xEnd - xStart) / 2.0;
            break;

        case Z_AXIS:
            double angleEnd = Math.atan2(height / 2.0 - yEnd,
                                         xEnd - width / 2.0);
            delta = (angleEnd - angleStart) * 180.0 / Math.PI;
            break;
        }

        go.translate(centerX, centerY, centerZ);
        go.rotate(delta, rX, rY, rZ);
        go.translate(-centerX, -centerY, -centerZ);
                        
        rerender();
    }

    //
    // Clamp function to Z range.
    //
    double fxyClamp(double x, double y)
    {
        double zVal = fxy(x, y);

        //
        // Chop off anything less than zMin or greater than zMax.
        //

        if(zVal < zClampMin) {
            return zClampMin;
        }
        else if(zVal > zClampMax) {
            return zClampMax;
        }
        else {
            return zVal;
        }
    }

    //
    // Derivative of function with respect to x.  (used to calc normals)
    //
    double dfxy_dx(double x, double y)
    {
        double delta = 0.0000001;
        
        return 1.0/(2.0 * delta) * (fxy(x + delta, y) - fxy(x - delta, y));
    }

    //
    // Derivative of function with respect to y.  (used to calc normals)
    //
    double dfxy_dy(double x, double y)
    {
        double delta = 0.0000001;
        
        return 1.0/(2.0 * delta) * (fxy(x, y + delta) - fxy(x, y - delta));
    }

    //
    // Normal vector at f(x, y)
    //
    void normal(double x, double y, double[] n)
    {
        double dx = dfxy_dx(x, y);
        double dy = dfxy_dy(x, y);
        double den = Math.sqrt(1.0 + dx * dx + dy * dy);

        n[0] = dx / den;
        n[1] = dy / den;
        n[2] = 1.0 / den;
    }

    //
    // Function creation.
    //
    void createFunction(double xMin, double xMax,
                        double yMin, double yMax,
                        double zMin, double zMax,
                        int xDiv, int yDiv)
    {
        int     i, j, count;
        double  x, y;
        double  xInc = (xMax - xMin) / xDiv;
        double  yInc = (yMax - yMin) / yDiv;

        triangleStripNum = xDiv;
        lineStripNum     = xDiv + yDiv + 2;        
        triangleStripData = new GoVertex[triangleStripNum];
        lineStripData = new GoVertex[lineStripNum];


        //
        // Save Z clamp values for use in fxyClamp().
        //

        zClampMin = zMin;
        zClampMax = zMax;

        //
        // Calc lineStripData.
        //

        count = 0;

        for(i = 0, x = xMin;
            i <= xDiv;
            i++, x += xInc, count++) {
            
            lineStripData[count] = new GoLineStrip(yDiv + 1);
            
            for(j = 0, y = yMin;
                j < yDiv + 1;
                j++, y += yInc) {
                
                lineStripData[count].xyz(j, x, y, fxyClamp(x, y));
            }
        }

        for(i = 0, y = yMin;
            i <= yDiv;
            i++, y += yInc, count++) {
            
            lineStripData[count] = new GoLineStrip(xDiv + 1);
            
            for(j = 0, x = xMin;
                j < xDiv + 1;
                j++, x += xInc) {
                
                lineStripData[count].xyz(j, x, y, fxyClamp(x, y));
            }
        }

        //
        // Calc triangleStripData.
        //

        count = 0;

        for(i = 0, x = xMin;
            i < xDiv;
            i++, x += xInc, count++) {
            
            triangleStripData[count] = new GoTriangleStrip(yDiv * 2 + 2, Go.NORMAL);

            triangleStripData[count].xyz(0, x, yMin, fxyClamp(x, yMin));
            normal(x, yMin, normal);
            triangleStripData[count].ijk(0, normal[0], normal[1], normal[2]);

            triangleStripData[count].xyz(1, x + xInc, yMin, fxyClamp(x + xInc, yMin));
            normal(x + xInc, yMin, normal);
            triangleStripData[count].ijk(1, normal[0], normal[1], normal[2]);
            
            for(j = 1, y = yMin + yInc;
                j <= yDiv;
                j++, y += yInc) {
                
                triangleStripData[count].xyz(j * 2, x, y, fxyClamp(x, y));
                normal(x, y, normal);
                triangleStripData[count].ijk(j * 2, normal[0], normal[1], normal[2]);
                
                triangleStripData[count].xyz(j * 2 + 1, x + xInc, y, fxyClamp(x + xInc, y));
                normal(x + xInc, y, normal);
                triangleStripData[count].ijk(j * 2 + 1, normal[0], normal[1], normal[2]);
            }
        }
    }

    //
    // Box creation.
    //
    void createBox(double xMin, double yMin, double zMin,
              double xMax, double yMax, double zMax)
    {
        top = new GoLineLoop(4);
        top.xyz(0, xMin, yMax, zMin);
        top.xyz(1, xMin, yMax, zMax);
        top.xyz(2, xMax, yMax, zMax);
        top.xyz(3, xMax, yMax, zMin);

        bottom = new GoLineLoop(4);
        bottom.xyz(0, xMin, yMin, zMin);
        bottom.xyz(1, xMin, yMin, zMax);
        bottom.xyz(2, xMax, yMin, zMax);
        bottom.xyz(3, xMax, yMin, zMin);

        sides = new GoLines(8);
        sides.xyz(0, xMin, yMin, zMin);
        sides.xyz(1, xMin, yMax, zMin);
        sides.xyz(2, xMax, yMin, zMin);
        sides.xyz(3, xMax, yMax, zMin);
        sides.xyz(4, xMin, yMin, zMax);
        sides.xyz(5, xMin, yMax, zMax);
        sides.xyz(6, xMax, yMin, zMax);
        sides.xyz(7, xMax, yMax, zMax);
    }

    //
    // Calculate distance between two points.
    //
    double distance(double x0, double y0, double z0,
                    double x1, double y1, double z1)
    {
        double dx = x1 - x0;
        double dy = y1 - y0;
        double dz = z1 - z0;
        
        return Math.sqrt(dx*dx + dy*dy + dz*dz);
    }
}

public class rotate extends Applet
{
    public void init()
    {
        //
        // Create the function to be displayed.
        //

        Function function = new Function(-3.0, 3.0,      // X range
                                         -3.0, 3.0,      // Y range
                                         -3.5, 1.5,      // Z clamp range
                                         18, 18,         // X-Y fish-net resolution
                                         3.9, -7.2, 6.0, // Eye location
                                         0.0, 0.0, 1.0,  // Up vector
                                         true);          // Perspective
        
        setLayout(new GridLayout(1, 1));
        add(function);
    }
}