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

class GoSierpinskiSponge extends GoInterface
{
    GoTriangles data;
    int         count;
    GoMatrix    currentModelview = new GoMatrix();
    int         xStart;
    int         yStart;
    boolean     createSponge;
    GoText      info;
    int         depth;  // recursion depth

    GoSierpinskiSponge(int depth)
    {   
        this.depth = depth;

        //
        // Background and foreground colors.
        //
        go.background(0.5, 0.0, 0.5);
        go.color(0, 1, 0);

        //
        // Enable a light. (Use the default values)
        //
        go.light(Go.LIGHT_0, true);

        //
        // Initial view.
        //
        go.rotate(20, 1, 1, 1);

        //
        // Don't want to see back facing triangles.
        //
        go.enable(Go.CULL);
        
        //
        // Initialize the information feedback text used in render
        // method during the creation of the Sierpinski sponge.
        //
        info = new GoText(0.0, 0.0, 0.0,                           // position
                          1.0, 0.0,                                // path
                          0.3, 0.3,                                // scale
                          GoText.LEFT_CENTER,                      // alignment
                          new GoHershey("hersheyFonts/futura.l"),  // font
                          "");                                     // text (nothing for now)

        //
        // Inform the render method that the Sierpinski sponge
        // data needs to be computed.
        //
        createSponge = true;
    }

    void sponge(int depth, double x0, double y0, double z0, double d)
    {
        int i, j, k;
        double x, y, z;

        if(depth > 0) {

            depth--;

            d /= 3.0;

            for(i = 0, x = x0; i < 3; i++, x += d) {
                for(j = 0, y = y0; j < 3; j++, y += d) {
                    for(k = 0, z = z0; k < 3; k++, z += d) {
                        
                        if(!(i == 1 && j == 1 && k == 1) &&
                           !(i == 0 && j == 1 && k == 1) &&
                           !(i == 2 && j == 1 && k == 1) &&
                           !(i == 1 && j == 0 && k == 1) &&
                           !(i == 1 && j == 2 && k == 1) &&
                           !(i == 1 && j == 1 && k == 0) &&
                           !(i == 1 && j == 1 && k == 2)) {

                            sponge(depth, x, y, z, d);
                        }
                    }
                }
            }
        }
        else {
            
            data.append(108);
            
            data.xyz(count*3 + 0, x0  , y0  , z0);    // 0
            data.xyz(count*3 + 1, x0  , y0  , z0+d);  // 1
            data.xyz(count*3 + 2, x0  , y0+d, z0+d);  // 3

            data.xyz(count*3 + 3, x0  , y0  , z0);    // 0
            data.xyz(count*3 + 4, x0  , y0+d, z0+d);  // 3
            data.xyz(count*3 + 5, x0  , y0+d, z0);    // 2

            data.xyz(count*3 + 6, x0+d, y0  , z0);    // 6
            data.xyz(count*3 + 7, x0+d, y0+d, z0+d);  // 5
            data.xyz(count*3 + 8, x0+d, y0  , z0+d);  // 7

            data.xyz(count*3 + 9, x0+d, y0  , z0);    // 6
            data.xyz(count*3 + 10, x0+d, y0+d, z0);   // 4
            data.xyz(count*3 + 11, x0+d, y0+d, z0+d); // 5

            data.xyz(count*3 + 12, x0  , y0+d, z0);   // 2
            data.xyz(count*3 + 13, x0  , y0+d, z0+d); // 3
            data.xyz(count*3 + 14, x0+d, y0+d, z0+d); // 5

            data.xyz(count*3 + 15, x0  , y0+d, z0);   // 2
            data.xyz(count*3 + 16, x0+d, y0+d, z0+d); // 5
            data.xyz(count*3 + 17, x0+d, y0+d, z0);   // 4

            data.xyz(count*3 + 18, x0  , y0  , z0);   // 0
            data.xyz(count*3 + 19, x0+d, y0  , z0+d); // 7
            data.xyz(count*3 + 20, x0  , y0  , z0+d); // 1

            data.xyz(count*3 + 21, x0  , y0  , z0);   // 0
            data.xyz(count*3 + 22, x0+d, y0  , z0);   // 6
            data.xyz(count*3 + 23, x0+d, y0  , z0+d); // 7

            data.xyz(count*3 + 24, x0  , y0  , z0+d); // 1
            data.xyz(count*3 + 25, x0+d, y0  , z0+d); // 7
            data.xyz(count*3 + 26, x0+d, y0+d, z0+d); // 5

            data.xyz(count*3 + 27, x0  , y0  , z0+d); // 1
            data.xyz(count*3 + 28, x0+d, y0+d, z0+d); // 5
            data.xyz(count*3 + 29, x0  , y0+d, z0+d); // 3
            
            data.xyz(count*3 + 30, x0  , y0  , z0);   // 0
            data.xyz(count*3 + 31, x0+d, y0+d, z0);   // 4
            data.xyz(count*3 + 32, x0+d, y0  , z0);   // 6

            data.xyz(count*3 + 33, x0  , y0  , z0);   // 0
            data.xyz(count*3 + 34, x0  , y0+d, z0);   // 2
            data.xyz(count*3 + 35, x0+d, y0+d, z0);   // 4
            
            count += 36;
        }
    }

    //
    // Helper function that determines if two numbers are
    // almost of the same value.
    //
    boolean same(double n1, double n2)
    {
        double fudge = 0.00001;

        if(n1 > n2 - fudge &&
           n1 < n2 + fudge) {
            return true;
        }

        return false;
    }

    //
    // Remove any triangles that are completely enclosed.
    //
    void minimize()
    {
        int triNum = data.vertexNumber() / 3;
        int i, j;

        // Set duplicate triangles to zero for all coordinates.
        for(i = 0; i < triNum - 14; i += 2) {
            for(j = i + 14; j < triNum; j += 4) {
            
                if(same(data.x(i*3),   data.x(j*3))   &&
                   same(data.x(i*3+1), data.x(j*3+2)) &&
                   same(data.x(i*3+2), data.x(j*3+1)) &&

                   same(data.y(i*3),   data.y(j*3))   &&
                   same(data.y(i*3+1), data.y(j*3+2)) &&
                   same(data.y(i*3+2), data.y(j*3+1)) &&

                   same(data.z(i*3),   data.z(j*3))   &&
                   same(data.z(i*3+1), data.z(j*3+2)) &&
                   same(data.z(i*3+2), data.z(j*3+1))) {
                    
                    data.xyz(i*3, 0.0, 0.0, 0.0);
                    data.xyz(i*3+1, 0.0, 0.0, 0.0);
                    data.xyz(i*3+2, 0.0, 0.0, 0.0);
                    data.xyz(i*3+3, 0.0, 0.0, 0.0);
                    data.xyz(i*3+4, 0.0, 0.0, 0.0);
                    data.xyz(i*3+5, 0.0, 0.0, 0.0);

                    data.xyz(j*3, 0.0, 0.0, 0.0);
                    data.xyz(j*3+1, 0.0, 0.0, 0.0);
                    data.xyz(j*3+2, 0.0, 0.0, 0.0);
                    data.xyz(j*3+3, 0.0, 0.0, 0.0);
                    data.xyz(j*3+4, 0.0, 0.0, 0.0);
                    data.xyz(j*3+5, 0.0, 0.0, 0.0);
                    
                    break;
                }
            }
        }

        // Pack the triangles.
        int count = 0;
        for(i = 0; i < triNum; i++) {

            if(!(data.x(i*3) == 0.0 &&
                 data.x(i*3+1) == 0.0 &&
                 data.x(i*3+2) == 0.0)) {

                data.xyz(count*3, data.x(i*3), data.y(i*3), data.z(i*3));
                data.xyz(count*3+1, data.x(i*3+1), data.y(i*3+1), data.z(i*3+1));
                data.xyz(count*3+2, data.x(i*3+2), data.y(i*3+2), data.z(i*3+2));
                count++;
            }
        }
        data.remove(count * 3, (triNum - count) * 3);
    }

    //
    // Interface render method.
    //
    // Function will first create then render the Sierpinski sponge.
    //
    public void render()
    {
        if(createSponge) {

            count = 0;
            data = new GoTriangles(0, Go.AUTO_NORMAL);

            go.push(Go.MODELVIEW);
            go.identity();

            info.text("Generating Sponge....");
            info.position(-1.8, 1.7, 0.0);
            go.clear(Go.IMAGE);
            info.render(go);
            swap();
            
            sponge(depth, -1.0, -1.0, -1.0, 2.0);
            
            info.text("Minimizing....");
            info.position(-1.8, 1.3, 0.0);
            info.render(go);
            swap();

            minimize();

            go.pop(Go.MODELVIEW);
            
            createSponge = false;

            rerender();
        }
        else {
            go.clear(Go.IMAGE);
            go.render(data);
            swap();
        }
    }

    //
    // Interface resized method.
    //
    public void resized(int width, int height)
    {
        //
        // Setup projection matrix.
        //

        go.push(Go.MATRIX_MODE);
        go.matrixMode(Go.PROJECTION);
        go.identity();
        if(width > height) {
            go.ortho(-2.0 * width / height, 2.0 * width / height,
                     -2.0, 2.0,
                     -2.0, 2.0);
        }
        else {
            go.ortho(-2.0, 2.0,
                     -2.0 * height / width, 2.0 * height / width,
                     -2.0, 2.0);
        }
        go.pop(Go.MATRIX_MODE);
    }
    
    //
    // Interface mousePressed method.
    //
    public void mousePressed(int x, int y)
    {
      //
      // Save values that will be used to track the
      // mouse in the mouseDragged() method.
      //
      
      xStart = x;
      yStart = y;
      
      go.getModelview(currentModelview);
    }

    //
    // Interface mouseDragged method.
    //
    public void mouseDragged(int x, int y)
    {
        int xEnd = x;
        int yEnd = y;

        //
        // Setup modelview matrix based on mouse position.
        //

        go.identity();
        double xTheta = (yEnd - yStart) / 2;
        double yTheta = (xEnd - xStart) / 2;
        go.rotate(xTheta, 1, 0, 0);
        go.rotate(yTheta, 0, 1, 0);

        //
        // Post multiply current modelview matrix.
        //

        go.multiply(currentModelview);

        //
        // Redisplay.
        //

        rerender();
    }
};

public class sierpinskiSponge extends Applet
{
    public void init()
    {
        // Fonts will be found relative to the html document
        // that includes this applet.
        GoFont.path(getDocumentBase());

        // Recursion depth.
        int depth = Integer.valueOf(getParameter("depth")).intValue();

        setLayout(new GridLayout(1, 1));
        add(new GoSierpinskiSponge(depth));
    }
}