/*
 * %W% %E%
 * 
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice, 
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 
 * Neither the name of Oracle or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

/*
 * %W% %E%
 */

package java2d.demos.Colors;

import java.awt.*;
import java2d.AnimatingSurface;

import static java.lang.Math.*;


/**
 * 3D objects with color & lighting translated, rotated and scaled.
 */
public class Rotator3D extends AnimatingSurface {

    private Objects3D objs[] = new Objects3D[3];
    private static final int[][][] polygons = 
             { 
        // Solid cube   
              {{5, 1, 15, 13, 21, 23, 15}, 
               {5, 2, 21, 13, 19, 27, 21},
               {5, 3, 23, 15, 17, 25, 23}, 
               {5, 4, 19, 13, 15, 17, 19},
               {5, 5, 27, 21, 23, 25, 27},
               {5, 6, 27, 19, 17, 25, 27}},
        // Polygonal faces cube
              {{5, 1, 21, 13, 19, 27, 21},
               {5, 5, 23, 15, 17, 25, 23}, 
               {4,0,15,14,16,15}, {7,6,16,14,13,12,18,17,16}, {4,0,12,19,18,12},
               {4,2,22,21,20,22}, {7,0,24,23,22,20,27,26,24}, {4,2,24,26,25,24},
               {4, 3, 15, 13, 23, 15}, {4, 0, 23, 13, 21, 23}, 
               {5, 0, 27, 26, 18, 19, 27}, {5, 4, 25, 17, 18, 26, 25}}, 
        // Octahedron
              {{4, 3, 18, 21, 16, 18}, {4, 1, 20, 16, 18, 20}, 
               {4, 1, 18, 21, 16, 18}, {4, 3, 20, 17, 19, 20}, 
               {4, 2, 20, 26, 27, 20}, {5, 3, 26, 18, 16, 27, 26}, 
               {5, 0, 17, 24, 25, 19, 17}, {4, 3, 21, 25, 24, 21}, 
               {4, 4, 18, 21, 22, 18}, {4, 2, 22, 21, 17, 22}, 
               {4, 5, 20, 23, 16, 20}, {4, 1, 20, 23, 19, 20}, 
               {4, 6, 21, 23, 16, 21}, {4, 4, 21, 23, 19, 21}, 
               {4, 5, 20, 18, 22, 20}, {4, 6, 20, 22, 17, 20}}
             };
    private static final double[][][] points = 
           { 
        // Points for solid cube & polygonal faces cube
            {{ 1,  0,  0}, {-1,  0,  0}, { 0,  1,  0}, { 0, -1,  0}, { 0,  0,  1}, 
             { 0,  0, -1}, { 1,  0,  0}, {-1,  0,  0}, { 0,  1,  0}, { 0, -1,  0}, 
             { 0,  0,  1}, { 0,  0, -1}, { 1,  1,  0}, { 1,  1,  1}, { 0,  1,  1}, 
             {-1,  1,  1}, {-1,  1,  0}, {-1,  1, -1}, { 0,  1, -1}, { 1,  1, -1}, 
             { 1, -1,  0}, { 1, -1,  1}, { 0, -1,  1}, {-1, -1,  1}, {-1, -1,  0}, 
             {-1, -1, -1}, { 0, -1, -1}, { 1, -1, -1}},
        // Points for octahedron
            {{0, 0, 1}, {0, 0, -1}, {-0.8165, 0.4714, 0.33333},
             {0.8165, -0.4714, -0.33333}, {0.8165, 0.4714, 0.33333},
             {-0.8165, -0.4714, -0.33333}, {0, -0.9428, 0.3333},
             {0, 0.9428, -0.33333}, {0, 0, 1}, {0, 0, -1},
             {-0.8165, 0.4714, 0.33333}, {0.8165, -0.4714, -0.33333}, 
             {0.8165, 0.4714, 0.33333}, {-0.8165, -0.4714, -0.33333},  
             {0, -0.9428, 0.33333}, {0, 0.9428, -0.33333},  
             {-1.2247, -0.7071, 1}, {1.2247, 0.7071, -1}, 
             {0, 1.4142, 1}, {0, -1.4142, -1}, {-1.2247, 0.7071, -1}, 
             {1.2247, -0.7071, 1}, {0.61237, 1.06066, 0}, 
             {-0.61237, -1.06066, 0}, {1.2247, 0, 0}, 
             {0.61237, -1.06066, 0}, {-0.61237, 1.06066, 0}, {-1.2247, 0, 0}}
           }; 
    private static final int[][][] faces = 
           { 
         // Solid cube
             {{1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 0}, {1, 5}},
         // Polygonal faces cube
             {{1, 0}, {1, 1}, {3, 2, 3, 4}, {3, 5, 6, 7}, {2,8,9}, {2,10,11}},
         // Octahedron
             {{1, 2}, {1, 3}, {2, 4, 5}, {2, 6, 7}, {2, 8, 9}, 
              {2, 10, 11}, {2, 12, 13}, {2, 14, 15}},
           };


    public Rotator3D() {
        setBackground(Color.white);
    }


    public void reset(int w, int h) {
        objs[0] = new Objects3D(polygons[0], points[0], faces[0], w, h);
        objs[1] = new Objects3D(polygons[1], points[0], faces[1], w, h);
        objs[2] = new Objects3D(polygons[2], points[1], faces[2], w, h);
    }


    public void step(int w, int h) {
        for (Objects3D obj : objs) {
            if (obj != null) {
                obj.step(w, h);
            }
        }
    }


    public void render(int w, int h, Graphics2D g2) {
        for (Objects3D obj : objs) {
            if (obj != null) {
                obj.render(g2);
            }
        }
    }


    public static void main(String argv[]) {
        createDemoFrame(new Rotator3D());
    }


/**
 * 3D Objects : Solid Cube, Cube & Octahedron with polygonal faces.
 */
public class Objects3D {

    private final int UP = 0;
    private final int DOWN = 1;
    private int[][] polygons;
    private int npoly;
    private double[][] points;
    private int npoint;
    private int[][] faces;
    private int nface;
    private int ncolour = 10;
    private Color[][] colours = new Color[ncolour][7];
    private double[] lightvec = {0, 1, 1}; 
    private double Zeye = 10;
    private double angle;
    private Matrix3D orient, tmp, tmp2, tmp3;
    private int scaleDirection;
    private double scale, scaleAmt;
    private double ix = 3.0, iy = 3.0;
    private double[][] rotPts;
    private int[][] scrPts;
    private int xx[] = new int[20];
    private int yy[] = new int[20];
    private double x, y;
    private int p, j;
    private int colour;
    private double bounce, persp;

 
    public Objects3D(int[][] polygons, 
                     double[][] points, 
                     int[][] faces, 
                     int w, 
                     int h) {

        this.polygons = polygons;
        this.points = points;
        this.faces = faces;
        npoly = polygons.length;
        npoint = points.length;
        nface = faces.length;

        x = w * random();
        y = h * random();

        ix = random() > 0.5 ? ix : -ix;
        iy = random() > 0.5 ? iy : -iy;

        rotPts = new double[npoint][3];
        scrPts = new int[npoint][2];
                    
        for (int i = 0; i < ncolour; i++) {
            int x = 255 - (ncolour - i - 1)*100/ncolour;
            Color[] c = {
                new Color(x,x,x), // white
                new Color(x,0,0), // red
                new Color(0,x,0), // green
                new Color(0,0,x), // blue
                new Color(x,x,0), // yellow
                new Color(0,x,x), // cyan
                new Color(x,0,x)  // magenta
            };
            colours[i] = c;
        }

        double len = sqrt(lightvec[0]*lightvec[0] + 
                          lightvec[1]*lightvec[1] + 
                          lightvec[2]*lightvec[2]);
        lightvec[0] = lightvec[0]/len;
        lightvec[1] = lightvec[1]/len;
        lightvec[2] = lightvec[2]/len;

        double max = 0;
        for (int i = 0; i < npoint; i++) {
            len = sqrt(points[i][0]*points[i][0] + 
                       points[i][1]*points[i][1] +
                       points[i][2]*points[i][2]);
            if (len >max) {
                max = len;
            }
        }
   
        for (int i = 0; i < nface; i++) {
            len = sqrt(points[i][0]*points[i][0] + 
                       points[i][1]*points[i][1] +
                       points[i][2]*points[i][2]);
            points[i][0] = points[i][0]/len;
            points[i][1] = points[i][1]/len;
            points[i][2] = points[i][2]/len;
        }

        orient = new Matrix3D();
        tmp    = new Matrix3D();
        tmp2   = new Matrix3D();
        tmp3   = new Matrix3D();
        tmp.Rotation(2,0,PI/50);
        CalcScrPts((double) w/3,(double) h/3, 0);

        scale = min(w/3/max/1.2, h/3/max/1.2);
        scaleAmt = scale;
        scale *= random() * 1.5;
        scaleDirection = scaleAmt < scale ? DOWN : UP;
    }

 
    private Color getColour(int f,int index) {
        colour = (int)((rotPts[f][0]*lightvec[0] +
                        rotPts[f][1]*lightvec[1] +
                        rotPts[f][2]*lightvec[2])  * ncolour); 
        if (colour < 0) {
            colour = 0;
        }
        if (colour > ncolour-1) {
            colour = ncolour-1;
        }
        return colours[colour][polygons[faces[f][index]][1]];
    }
      

    private void CalcScrPts(double x, double y, double z) {
        for (p = 0; p < npoint; p++) {

            rotPts[p][2] =   points[p][0]*orient.M[2][0]
                           + points[p][1]*orient.M[2][1]
                           + points[p][2]*orient.M[2][2];

            rotPts[p][0] =   points[p][0]*orient.M[0][0]
                           + points[p][1]*orient.M[0][1]
                           + points[p][2]*orient.M[0][2];

            rotPts[p][1] = - points[p][0]*orient.M[1][0]
                           - points[p][1]*orient.M[1][1]
                           - points[p][2]*orient.M[1][2];
        }
        for (p = nface; p < npoint; p++) {
            rotPts[p][2] += z;
            persp = (Zeye - rotPts[p][2])/(scale*Zeye);
            scrPts[p][0] = (int)(rotPts[p][0]/persp+x);
            scrPts[p][1] = (int)(rotPts[p][1]/persp+y);
        }
    }


    private boolean faceUp(int f) {
        return (rotPts[f][0] * rotPts[nface+f][0] + 
                rotPts[f][1] * rotPts[nface+f][1] + 
                rotPts[f][2] *(rotPts[nface+f][2]-Zeye) < 0);
    }
    

    public void step(int w, int h) {
        x += ix;
        y += iy;
        if (x > w-scale) {
            x = w-scale-1;
            ix = -w/100 - 1;
        }
        if (x-scale < 0) {
            x = 2+scale;
            ix = w/100 + random()*3;
        }
        if (y > h-scale) {
            y = h-scale-2;
            iy = -h/100 - 1;
        }
        if (y-scale < 0) {
            y = 2+scale;
            iy = h/100 + random()*3;
        }

        angle += random() * 0.15;
        tmp3.Rotation(1, 2, angle);
        tmp2.Rotation(1, 0, angle*sqrt(2)/2);
        tmp.Rotation(0, 2, angle*PI/4);
        orient.M = tmp3.Times(tmp2.Times(tmp.M));
        bounce = abs(cos(0.5*(angle)))*2-1;

        if (scale > scaleAmt*1.4) {
            scaleDirection = DOWN;
        }
        if (scale < scaleAmt*0.4) {
            scaleDirection = UP;
        }
        if (scaleDirection == UP) {
            scale += random();
        }
        if (scaleDirection == DOWN) {
            scale -= random();
        }

        CalcScrPts(x, y, bounce);
    }


    public void render(Graphics2D g2) {
        for (int f = 0;f < nface; f++) {
            if (faceUp(f)) {
                for (j = 1;j < faces[f][0]+1; j++) {
                    DrawPoly(g2, faces[f][j], getColour(f,j));
                }
            }
        } 
    } 


    private void DrawPoly(Graphics2D g2, int poly, Color colour) {
        for (int p = 2; p < polygons[poly][0]+2; p++) {
            xx[p-2] = scrPts[polygons[poly][p]][0];
            yy[p-2] = scrPts[polygons[poly][p]][1];
        }
        g2.setColor(colour); 
        g2.fillPolygon(xx,yy,polygons[poly][0]);
        g2.setColor(Color.black); 
        g2.drawPolygon(xx,yy,polygons[poly][0]);
    }


/** 
 * A 3D matrix object.
 */
public class Matrix3D {

    public double[][] M = { { 1, 0, 0 },
                            { 0, 1, 0 },
                            { 0, 0, 1 } };
    private double[][] tmp = new double[3][3];
    private int row, col, k;
 
    public void Rotation(int i, int j, double angle) {
        for (row = 0; row < 3; row++) {
            for (col = 0; col <3; col++) {
                if (row != col) { 
                    M[row][col] = 0.0; 
                } else { 
                    M[row][col] = 1.0; 
                }
            }
        }
        M[i][i] =  cos(angle);
        M[j][j] =  cos(angle);
        M[i][j] =  sin(angle);
        M[j][i] = -sin(angle);
    }

    public double[][] Times(double[][] N) {
        for (row = 0; row < 3; row++) {
            for (col = 0; col < 3; col++) {
                tmp[row][col] = 0.0;
                for (k = 0; k < 3; k++) {
                    tmp[row][col] += M[row][k] * N[k][col];
                }
            }
        }
        return tmp;
    }

} // End Matrix3D

} // End Objects3D

} // End Rotator3D
