User:Gizmo/Rubik's Cubes/AnimCube.java

From Sodawiki

< User:Gizmo | Rubik's Cubes
Revision as of 02:09, 5 June 2007 by Gizmo (Talk | contribs)
(diff) ←Older revision | view current revision (diff) | Newer revision→ (diff)

import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.Hashtable; import java.net.URL; import java.net.MalformedURLException; import java.io.*;

/**

* @author Josef Jelinek
* @version 3.5b
*/

public final class AnimCube extends Applet implements Runnable, MouseListener, MouseMotionListener {

 // external configuration
 private final Hashtable config = new Hashtable();
 // background colors
 private Color bgColor;
 private Color bgColor2;
 private Color hlColor;
 private Color textColor;
 private Color buttonBgColor;
 // cube colors
 private final Color[] colors = new Color[24];
 // cube facelets
 private final int[][] cube = new int[6][9];
 private final int[][] initialCube = new int[6][9];
 // normal vectors
 private static final double[][] faceNormals = {
   { 0.0, -1.0,  0.0}, // U
   { 0.0,  1.0,  0.0}, // D
   { 0.0,  0.0, -1.0}, // F
   { 0.0,  0.0,  1.0}, // B
   {-1.0,  0.0,  0.0}, // L
   { 1.0,  0.0,  0.0}  // R
 };
 // vertex co-ordinates
 private static final double[][] cornerCoords = {
   {-1.0, -1.0, -1.0}, // UFL
   { 1.0, -1.0, -1.0}, // UFR
   { 1.0, -1.0,  1.0}, // UBR
   {-1.0, -1.0,  1.0}, // UBL
   {-1.0,  1.0, -1.0}, // DFL
   { 1.0,  1.0, -1.0}, // DFR
   { 1.0,  1.0,  1.0}, // DBR
   {-1.0,  1.0,  1.0}  // DBL
 };
 // vertices of each face
 private static final int[][] faceCorners = {
   {0, 1, 2, 3}, // U: UFL UFR UBR UBL
   {4, 7, 6, 5}, // D: DFL DBL DBR DFR
   {0, 4, 5, 1}, // F: UFL DFL DFR UFR
   {2, 6, 7, 3}, // B: UBR DBR DBL UBL
   {0, 3, 7, 4}, // L: UFL UBL DBL DFL
   {1, 5, 6, 2}  // R: UFR DFR DBR UBR
 };
 // corresponding corners on the opposite face
 private static final int[][] oppositeCorners = {
   {0, 3, 2, 1}, // U->D
   {0, 3, 2, 1}, // D->U
   {3, 2, 1, 0}, // F->B
   {3, 2, 1, 0}, // B->F
   {0, 3, 2, 1}, // L->R
   {0, 3, 2, 1}, // R->L
 };
 // faces adjacent to each face
 private static final int[][] adjacentFaces = {
   {2, 5, 3, 4}, // U: F R B L
   {4, 3, 5, 2}, // D: L B R F
   {4, 1, 5, 0}, // F: L D R U
   {5, 1, 4, 0}, // B: R D L U
   {0, 3, 1, 2}, // L: U B D F
   {2, 1, 3, 0}  // R: F D B U
 };
 // current twisted layer
 private int twistedLayer;
 private int twistedMode;
 // directions of facelet cycling for all faces
 private static final int[] faceTwistDirs = {1, 1, -1, -1, -1, -1};
 // initial observer co-ordinate axes (view)
 private final double[] eye = {0.0, 0.0, -1.0};
 private final double[] eyeX = {1.0, 0.0, 0.0}; // (sideways)
 private final double[] eyeY = new double[3]; // (vertical)
 private final double[] initialEye = new double[3];
 private final double[] initialEyeX = new double[3];
 private final double[] initialEyeY = new double[3];
 // angle of rotation of the twistedLayer
 private double currentAngle; // edited angle of twisted layer
 private double originalAngle; // angle of twisted layer
 // animation speed
 private int speed;
 private int doubleSpeed;
 // current state of the program
 private boolean natural = true; // cube is compact, no layer is twisted
 private boolean toTwist; // layer can be twisted
 private boolean interrupted; // thread was interrupted
 private boolean restarted; // animation was stopped
 private boolean mirrored; // mirroring of the cube view
 private boolean editable; // editation of the cube with a mouse
 private boolean twisting; // a user twists a cube layer
 private boolean spinning; // an animation twists a cube layer
 private boolean animating; // animation run
 private boolean dragging; // progress bar is controlled
 private boolean demo; // demo mode
 private int persp; // perspective deformation
 private double scale; // cube scale
 private int align; // cube alignment (top, center, bottom)
 private boolean hint;
 private double faceShift;
 // move sequence data
 private int[][] move;
 private int[][] demoMove;
 private int curMove;
 private int movePos;
 private int moveDir;
 private boolean moveOne;
 private boolean moveAnimated;
 private int metric;
 private String[] infoText;
 private int curInfoText;
 // state of buttons
 private int buttonBar; // button bar mode
 private int buttonHeight;
 private boolean drawButtons = true;
 private boolean pushed;
 private int buttonPressed = -1;
 private int progressHeight = 6;
 private int textHeight;
 private int moveText;
 private boolean outlined = true;
 // transformation tables for compatibility with Lars's applet
 private static final int[] posFaceTransform = {3, 2, 0, 5, 1, 4};
 private static final int[][] posFaceletTransform = {
   {6, 3, 0, 7, 4, 1, 8, 5, 2}, // B +27
   {2, 5, 8, 1, 4, 7, 0, 3, 6}, // F +18
   {0, 1, 2, 3, 4, 5, 6, 7, 8}, // U +0
   {0, 1, 2, 3, 4, 5, 6, 7, 8}, // R +45
   {6, 3, 0, 7, 4, 1, 8, 5, 2}, // D +9
   {0, 1, 2, 3, 4, 5, 6, 7, 8}  // L +36
 };
 // buffer to store hexa-digits
 private final int[] hex = new int[6];
 public void init() {
   // register to receive all mouse events
   addMouseListener(this);
   addMouseMotionListener(this);
   // setup colors
   colors[0] = new Color(255, 128, 64);   // 0 - light orange
   colors[1] = new Color(255, 0, 0);      // 1 - pure red
   colors[2] = new Color(0, 255, 0);      // 2 - pure green
   colors[3] = new Color(0, 0, 255);      // 3 - pure blue
   colors[4] = new Color(153, 153, 153);  // 4 - white grey
   colors[5] = new Color(170, 170, 68);   // 5 - yellow grey
   colors[6] = new Color(187, 119, 68);   // 6 - orange grey
   colors[7] = new Color(153, 68, 68);    // 7 - red grey
   colors[8] = new Color(68, 119, 68);    // 8 - green grey
   colors[9] = new Color(0, 68, 119);     // 9 - blue grey
   colors[10] = new Color(255, 255, 255); // W - white
   colors[11] = new Color(255, 255, 0);   // Y - yellow
   colors[12] = new Color(255, 96, 32);   // O - orange
   colors[13] = new Color(208, 0, 0);     // R - red
   colors[14] = new Color(0, 144, 0);     // G - green
   colors[15] = new Color(32, 64, 208);   // B - blue
   colors[16] = new Color(176, 176, 176); // L - light gray
   colors[17] = new Color(80, 80, 80);    // D - dark gray
   colors[18] = new Color(255, 0, 255);   // M - magenta
   colors[19] = new Color(0, 255, 255);   // C - cyan
   colors[20] = new Color(255, 160, 192); // P - pink
   colors[21] = new Color(32, 255, 16);   // N - light green
   colors[22] = new Color(0, 0, 0);       // K - black
   colors[23] = new Color(128, 128, 128); // . - gray
   // create animation thread
   animThread = new Thread(this, "Cube Animator");
   animThread.start();
   // setup default configuration
   String param = getParameter("config");
   if (param != null) {
     try {
       URL url = new URL(getDocumentBase(), param);
       InputStream input = url.openStream();
       BufferedReader reader = new BufferedReader(new InputStreamReader(input));
       String line = reader.readLine();
       while (line != null) {
         int pos = line.indexOf('=');
         if (pos > 0) {
           String key = line.substring(0, pos).trim();
           String value = line.substring(pos + 1).trim();
           config.put(key, value);
         }
         line = reader.readLine();
       }
       reader.close();
     }
     catch (MalformedURLException ex) {
       System.err.println("Malformed URL: " + param + ": " + ex);
     }
     catch (IOException ex) {
       System.err.println("Input error: " + param + ": " + ex);
     }
   }
   // setup window background color
   param = getParameter("bgcolor");
   if (param != null && param.length() == 6) {
     for (int i = 0; i < 6; i++) {
       for (int j = 0; j < 16; j++) {
         if (Character.toLowerCase(param.charAt(i)) == "0123456789abcdef".charAt(j)) {
           hex[i] = j;
           break;
         }
       }
     }
     bgColor = new Color(hex[0] * 16 + hex[1], hex[2] * 16 + hex[3], hex[4] * 16 + hex[5]);
   }
   else
     bgColor = Color.gray;
   // setup button bar background color
   param = getParameter("butbgcolor");
   if (param != null && param.length() == 6) {
     for (int i = 0; i < 6; i++) {
       for (int j = 0; j < 16; j++) {
         if (Character.toLowerCase(param.charAt(i)) == "0123456789abcdef".charAt(j)) {
           hex[i] = j;
           break;
         }
       }
     }
     buttonBgColor = new Color(hex[0] * 16 + hex[1], hex[2] * 16 + hex[3], hex[4] * 16 + hex[5]);
   }
   else
     buttonBgColor = bgColor;
   // custom colors
   param = getParameter("colors");
   if (param != null) {
     for (int k = 0; k < 10 && k < param.length() / 6; k++) {
       for (int i = 0; i < 6; i++) {
         for (int j = 0; j < 16; j++) {
           if (Character.toLowerCase(param.charAt(k * 6 + i)) == "0123456789abcdef".charAt(j)) {
             hex[i] = j;
             break;
           }
         }
       }
       colors[k] = new Color(hex[0] * 16 + hex[1], hex[2] * 16 + hex[3], hex[4] * 16 + hex[5]);
     }
   }
   // clean the cube
   for (int i = 0; i < 6; i++)
     for (int j = 0; j < 9; j++)
       cube[i][j] = i + 10;
   String initialPosition = "lluu";
   // setup color configuration of the solved cube
   param = getParameter("colorscheme");
   if (param != null && param.length() == 6) {
     for (int i = 0; i < 6; i++) { // udfblr
       int color = 23;
       for (int j = 0; j < 23; j++) {
         if (Character.toLowerCase(param.charAt(i)) == "0123456789wyorgbldmcpnk".charAt(j)) {
           color = j;
           break;
         }
       }
       for (int j = 0; j < 9; j++)
         cube[i][j] = color;
     }
   }
   // setup facelets - compatible with Lars's applet
   param = getParameter("pos");
   if (param != null && param.length() == 54) {
     initialPosition = "uuuuff";
     if (bgColor == Color.gray)
       bgColor = Color.white;
     for (int i = 0; i < 6; i++) {
       int ti = posFaceTransform[i];
       for (int j = 0; j < 9; j++) {
         int tj = posFaceletTransform[i][j];
         cube[ti][tj] = 23;
         for (int k = 0; k < 14; k++) {
           // "abcdefgh" ~ "gbrwoyld"
           if (param.charAt(i * 9 + j) == "DFECABdfecabgh".charAt(k)) {
             cube[ti][tj] = k + 4;
             break;
           }
         }
       }
     }
   }
   // setup color facelets
   param = getParameter("facelets");
   if (param != null && param.length() == 54) {
     for (int i = 0; i < 6; i++) {
       for (int j = 0; j < 9; j++) {
         cube[i][j] = 23;
         for (int k = 0; k < 23; k++) {
           if (Character.toLowerCase(param.charAt(i * 9 + j)) == "0123456789wyorgbldmcpnk".charAt(k)) {
             cube[i][j] = k;
             break;
           }
         }
       }
     }
   }
   // setup move sequence (and info texts)
   param = getParameter("move");
   move = (param == null ? new int[0][0] : getMove(param, true));
   movePos = 0;
   curInfoText = -1;
   // setup initial move sequence
   param = getParameter("initmove");
   if (param != null) {
     int[][] initialMove = param.equals("#") ? move : getMove(param, false);
     if (initialMove.length > 0)
       doMove(cube, initialMove[0], 0, initialMove[0].length, false);
   }
   // setup initial reversed move sequence
   param = getParameter("initrevmove");
   if (param != null) {
     int[][] initialReversedMove = param.equals("#") ? move : getMove(param, false);
     if (initialReversedMove.length > 0)
       doMove(cube, initialReversedMove[0], 0, initialReversedMove[0].length, true);
   }
   // setup initial reversed move sequence
   param = getParameter("demo");
   if (param != null) {
     demoMove = param.equals("#") ? move : getMove(param, true);
     if (demoMove.length > 0 && demoMove[0].length > 0)
       demo = true;
   }
   // setup initial cube position
   param = getParameter("position");
   vNorm(vMul(eyeY, eye, eyeX));
   if (param == null)
     param = initialPosition;
   double pi12 = Math.PI / 12;
   for (int i = 0; i < param.length(); i++) {
     double angle = pi12;
     switch (Character.toLowerCase(param.charAt(i))) {
      case 'd':
       angle = -angle;
      case 'u':
       vRotY(eye, angle);
       vRotY(eyeX, angle);
       break;
      case 'f':
       angle = -angle;
      case 'b':
       vRotZ(eye, angle);
       vRotZ(eyeX, angle);
       break;
      case 'l':
       angle = -angle;
      case 'r':
       vRotX(eye, angle);
       vRotX(eyeX, angle);
       break;
     }
   }
   vNorm(vMul(eyeY, eye, eyeX)); // fix eyeY
   // setup quarter-turn speed and double-turn speed
   speed = 0;
   doubleSpeed = 0;
   param = getParameter("speed");
   if (param != null)
     for (int i = 0; i < param.length(); i++)
       if (param.charAt(i) >= '0' && param.charAt(i) <= '9')
         speed = speed * 10 + (int)param.charAt(i) - '0';
   param = getParameter("doublespeed");
   if (param != null)
     for (int i = 0; i < param.length(); i++)
       if (param.charAt(i) >= '0' && param.charAt(i) <= '9')
         doubleSpeed = doubleSpeed * 10 + (int)param.charAt(i) - '0';
   if (speed == 0)
     speed = 10;
   if (doubleSpeed == 0)
     doubleSpeed = speed * 3 / 2;
   // perspective deformation
   persp = 0;
   param = getParameter("perspective");
   if (param == null)
     persp = 2;
   else
     for (int i = 0; i < param.length(); i++)
       if (param.charAt(i) >= '0' && param.charAt(i) <= '9')
         persp = persp * 10 + (int)param.charAt(i) - '0';
   // cube scale
   int intscale = 0;
   param = getParameter("scale");
   if (param != null)
     for (int i = 0; i < param.length(); i++)
       if (param.charAt(i) >= '0' && param.charAt(i) <= '9')
         intscale = intscale * 10 + (int)param.charAt(i) - '0';
   scale = 1.0 / (1.0 + intscale / 10.0);
   // hint displaying
   hint = false;
   param = getParameter("hint");
   if (param != null) {
     hint = true;
     faceShift = 0.0;
     for (int i = 0; i < param.length(); i++)
       if (param.charAt(i) >= '0' && param.charAt(i) <= '9')
         faceShift = faceShift * 10 + (int)param.charAt(i) - '0';
     if (faceShift < 1.0)
       hint = false;
     else
       faceShift /= 10.0;
   }
   // appearance and configuration of the button bar
   buttonBar = 1;
   buttonHeight = 13;
   progressHeight = move.length == 0 ? 0 : 6;
   param = getParameter("buttonbar");
   if ("0".equals(param)) {
     buttonBar = 0;
     buttonHeight = 0;
     progressHeight = 0;
   }
   else if ("1".equals(param))
     buttonBar = 1;
   else if ("2".equals(param) || move.length == 0) {
     buttonBar = 2;
     progressHeight = 0;
   }
   // whether the cube can be edited with mouse
   param = getParameter("edit");
   if ("0".equals(param))
     editable = false;
   else
     editable = true;
   // displaying the textual representation of the move
   param = getParameter("movetext");
   if ("1".equals(param))
     moveText = 1;
   else if ("2".equals(param))
     moveText = 2;
   else if ("3".equals(param))
     moveText = 3;
   else if ("4".equals(param))
     moveText = 4;
   else
     moveText = 0;
   // how texts are displayed
   param = getParameter("fonttype");
   if (param == null || "1".equals(param))
     outlined = true;
   else
     outlined = false;
   // metric
   metric = 0;
   param = getParameter("metric");
   if (param != null) {
     if ("1".equals(param)) // quarter-turn
       metric = 1;
     else if ("2".equals(param)) // face-turn
       metric = 2;
     else if ("3".equals(param)) // slice-turn
       metric = 3;
   }
   // metric
   align = 1;
   param = getParameter("align");
   if (param != null) {
     if ("0".equals(param)) // top
       align = 0;
     else if ("1".equals(param)) // center
       align = 1;
     else if ("2".equals(param)) // bottom
       align = 2;
   }
   // setup initial values
   for (int i = 0; i < 6; i++)
     for (int j = 0; j < 9; j++)
       initialCube[i][j] = cube[i][j];
   for (int i = 0; i < 3; i++) {
     initialEye[i] = eye[i];
     initialEyeX[i] = eyeX[i];
     initialEyeY[i] = eyeY[i];
   }
   // setup colors (contrast)
   int red = bgColor.getRed();
   int green = bgColor.getGreen();
   int blue = bgColor.getBlue();
   int average = (red * 299 + green * 587 + blue * 114) / 1000;
   if (average < 128) {
     textColor = Color.white;
     hlColor = bgColor.brighter();
     hlColor = new Color(hlColor.getBlue(), hlColor.getRed(), hlColor.getGreen());
   }
   else {
     textColor = Color.black;
     hlColor = bgColor.darker();
     hlColor = new Color(hlColor.getBlue(), hlColor.getRed(), hlColor.getGreen());
   }
   bgColor2 = new Color(red / 2, green / 2, blue / 2);
   curInfoText = -1;
   if (demo)
     startAnimation(-1);
 } // init()
 public String getParameter(String name) {
   String parameter = super.getParameter(name);
   if (parameter == null)
     return (String)config.get(name);
   return parameter;
 }
 private static final int[] moveModes = {
   0, 0, 0, 0, 0, 0, // UDFBLR
   1, 1, 1,          // ESM
   3, 3, 3, 3, 3, 3, // XYZxyz
   2, 2, 2, 2, 2, 2  // udfblr
 };
 private static final int[] moveCodes = {
   0, 1, 2, 3, 4, 5, // UDFBLR
   1, 2, 4,          // ESM
   5, 2, 0, 5, 2, 0, // XYZxyz
   0, 1, 2, 3, 4, 5  // udfblr
 };
 private int[][] getMove(String sequence, boolean info) {
   if (info) {
     int inum = 0;
     int pos = sequence.indexOf('{');
     while (pos != -1) {
       inum++;
       pos = sequence.indexOf('{', pos + 1);
     }
     if (infoText == null) {
       curInfoText = 0;
       infoText = new String[inum];
     }
     else {
       String[] infoText2 = new String[infoText.length + inum];
       for (int i = 0; i < infoText.length; i++)
         infoText2[i] = infoText[i];
       curInfoText = infoText.length;
       infoText = infoText2;
     }
   }
   int num = 1;
   int pos = sequence.indexOf(';');
   while (pos != -1) {
     num++;
     pos = sequence.indexOf(';', pos + 1);
   }
   int[][] move = new int[num][];
   int lastPos = 0;
   pos = sequence.indexOf(';');
   num = 0;
   while (pos != -1) {
     move[num++] = getMovePart(sequence.substring(lastPos, pos), info);
     lastPos = pos + 1;
     pos = sequence.indexOf(';', lastPos);
   }
   move[num] = getMovePart(sequence.substring(lastPos), info);
   return move;
 }
 private static final char[] modeChar = {'m', 't', 'c', 's', 'a'};
 private int[] getMovePart(String sequence, boolean info) {
   int length = 0;
   int[] move = new int[sequence.length()]; // overdimmensioned
   for (int i = 0; i < sequence.length(); i++) {
     if (sequence.charAt(i) == '.') {
       move[length] = -1;
       length++;
     }
     else if (sequence.charAt(i) == '{') {
       i++;
       String s = "";
       while (i < sequence.length()) {
         if (sequence.charAt(i) == '}')
           break;
         if (info)
           s += sequence.charAt(i);
         i++;
       }
       if (info) {
         infoText[curInfoText] = s;
         move[length] = 1000 + curInfoText;
         curInfoText++;
         length++;
       }
     }
     else {
       for (int j = 0; j < 21; j++) {
         if (sequence.charAt(i) == "UDFBLRESMXYZxyzudfblr".charAt(j)) {
           i++;
           int mode = moveModes[j];
           move[length] = moveCodes[j] * 24;
           if (i < sequence.length()) {
             if (moveModes[j] == 0) { // modifiers for basic characters UDFBLR
               for (int k = 0; k < modeChar.length; k++) {
                 if (sequence.charAt(i) == modeChar[k]) {
                   mode = k + 1;
                   i++;
                   break;
                 }
               }
             }
           }
           move[length] += mode * 4;
           if (i < sequence.length()) {
             if (sequence.charAt(i) == '1')
               i++;
             else if (sequence.charAt(i) == '\ || sequence.charAt(i) == '3') {
               move[length] += 2;
               i++;
             }
             else if (sequence.charAt(i) == '2') {
               i++;
               if (i < sequence.length() && sequence.charAt(i) == '\) {
                 move[length] += 3;
                 i++;
               }
               else
                 move[length] += 1;
             }
           }
           length++;
           i--;
           break;
         }
       }
     }
   }
   int[] returnMove = new int[length];
   for (int i = 0; i < length; i++)
     returnMove[i] = move[i];
   return returnMove;
 }
 private String moveText(int[] move, int start, int end) {
   if (start >= move.length)
     return "";
   String s = "";
   for (int i = start; i < end; i++)
     s += turnText(move, i);
   return s;
 }
 private static final String[][][] turnSymbol = {
   { // "standard" notation
     {"U", "D", "F", "B", "L", "R"},
     {"Um", "Dm", "Fm", "Bm", "Lm", "Rm"},
     {"Ut", "Dt", "Ft", "Bt", "Lt", "Rt"},
     {"Uc", "Dc", "Fc", "Bc", "Lc", "Rc"},
     {"Us", "Ds", "Fs", "Bs", "Ls", "Rs"},
     {"Ua", "Da", "Fa", "Ba", "La", "Ra"}
   },
   { // "reduced" notation
     {"U", "D", "F", "B", "L", "R"},
     {"~E", "E", "S", "~S", "M", "~M"},
     {"u", "d", "f", "b", "l", "r"},
     {"Z", "~Z", "Y", "~Y", "~X", "X"},
     {"Us", "Ds", "Fs", "Bs", "Ls", "Rs"},
     {"Ua", "Da", "Fa", "Ba", "La", "Ra"}
   },
   { // "reduced" notation - swapped Y and Z
     {"U", "D", "F", "B", "L", "R"},
     {"~E", "E", "S", "~S", "M", "~M"},
     {"u", "d", "f", "b", "l", "r"},
     {"Y", "~Y", "Z", "~Z", "~X", "X"},
     {"Us", "Ds", "Fs", "Bs", "Ls", "Rs"},
     {"Ua", "Da", "Fa", "Ba", "La", "Ra"}
   },
   { // another reduced notation
     {"U", "D", "F", "B", "L", "R"},
     {"u", "d", "f", "b", "l", "r"},
     {"Uu", "Dd", "Ff", "Bb", "Ll", "Rr"},
     {"QU", "QD", "QF", "QB", "QL", "QR"},
     {"UD'", "DU'", "FB'", "BF'", "LR'", "RL'"},
     {"UD", "DU", "FB", "BF", "LR", "RL"}
   }
 };
 private static final String[] modifierStrings = {"", "2", "'", "2'"};
 private String turnText(int[] move, int pos) {
   if (pos >= move.length)
     return "";
   if (move[pos] >= 1000)
     return "";
   if (move[pos] == -1)
     return ".";
   String s = turnSymbol[moveText - 1][move[pos] / 4 % 6][move[pos] / 24];
   if (s.charAt(0) == '~')
     return s.substring(1) + modifierStrings[(move[pos] + 2) % 4];
   return s + modifierStrings[move[pos] % 4];
 }
 private static final String[] metricChar = {"", "q", "f", "s"};
 private static int realMoveLength(int[] move) {
   int length = 0;
   for (int i = 0; i < move.length; i++)
     if (move[i] < 1000)
       length++;
   return length;
 }
 private static int realMovePos(int[] move, int pos) {
   int rpos = 0;
   for (int i = 0; i < pos; i++)
     if (move[i] < 1000)
       rpos++;
   return rpos;
 }
 private static int arrayMovePos(int[] move, int realPos) {
   int pos = 0;
   int rpos = 0;
   while (true) {
     while (pos < move.length && move[pos] >= 1000)
       pos++;
     if (rpos == realPos)
       break;
     if (pos < move.length) {
       rpos++;
       pos++;
     }
   }
   return pos;
 }
 private int moveLength(int[] move, int end) {
   int length = 0;
   for (int i = 0; i < move.length && (i < end || end < 0); i++)
     length += turnLength(move[i]);
   return length;
 }
 private int turnLength(int turn) {
   if (turn < 0 || turn >= 1000)
     return 0;
   int modifier = turn % 4;
   int mode = turn / 4 % 6;
   int n = 1;
   switch (metric) {
    case 1: // quarter-turn metric
     if (modifier == 1 || modifier == 3)
       n *= 2;
    case 2: // face-turn metric
     if (mode == 1 || mode == 4 || mode == 5)
       n *= 2;
    case 3: // slice-turn metric
     if (mode == 3)
       n = 0;
   }
   return n;
 }
 private void initInfoText(int[] move) {
   if (move.length > 0 && move[0] >= 1000)
     curInfoText = move[0] - 1000;
   else
     curInfoText = -1;
 }
 private void doMove(int[][] cube, int[] move, int start, int length, boolean reversed) {
   int position = reversed ? start + length : start;
   while (true) {
     if (reversed) {
       if (position <= start)
         break;
       position--;
     }
     if (move[position] >= 1000) {
       curInfoText = reversed ? -1 : move[position] - 1000;
     }
     else if (move[position] >= 0) {
       int modifier = move[position] % 4 + 1;
       int mode = move[position] / 4 % 6;
       if (modifier == 4) // reversed double turn
         modifier = 2;
       if (reversed)
         modifier = 4 - modifier;
       twistLayers(cube, move[position] / 24, modifier, mode);
     }
     if (!reversed) {
       position++;
       if (position >= start + length)
         break;
     }
   }
 }
 private Thread animThread = null; // thread to perform the animation
 private void startAnimation(int mode) {
   synchronized (animThread) {
     stopAnimation();
     if (!demo && (move.length == 0 || move[curMove].length == 0))
       return;
     if (demo && (demoMove.length == 0 || demoMove[0].length == 0))
       return;
     moveDir = 1;
     moveOne = false;
     moveAnimated = true;
     switch (mode) {
      case 0: // play forward
       break;
      case 1: // play backward
       moveDir = -1;
       break;
      case 2: // step forward
       moveOne = true;
       break;
      case 3: // step backward
       moveDir = -1;
       moveOne = true;
       break;
      case 4: // fast forward
       moveAnimated = false;
       break;
     }
     //System.err.println("start: notify");
     animThread.notify();
   }
 }
 public void stopAnimation() {
   synchronized (animThread) {
     restarted = true;
     //System.err.println("stop: notify");
     animThread.notify();
     try {
       //System.err.println("stop: wait");
       animThread.wait();
       //System.err.println("stop: run");
     }
     catch (InterruptedException e) {
       interrupted = true;
     }
     restarted = false;
   }
 }
 public void run() {
   synchronized (animThread) {
     interrupted = false;
     do {
       if (restarted) {
         //System.err.println("run: notify");
         animThread.notify();
       }
       try {
         //System.err.println("run: wait");
         animThread.wait();
         //System.err.println("run: run");
       }
       catch (InterruptedException e) {
         break;
       }
       if (restarted)
         continue;
       boolean restart = false;
       animating = true;
       drawButtons = true;
       int[] mv = demo ? demoMove[0] : move[curMove];
       if (moveDir > 0) {
         if (movePos >= mv.length) {
           movePos = 0;
           initInfoText(mv);
         }
       }
       else {
         curInfoText = -1;
         if (movePos == 0)
           movePos = mv.length;
       }
       while (true) {
         if (moveDir < 0) {
           if (movePos == 0)
             break;
           movePos--;
         }
         if (mv[movePos] == -1) {
           repaint();
           if (!moveOne)
             sleep(33 * speed);
         }
         else if (mv[movePos] >= 1000) {
           curInfoText = moveDir > 0 ? mv[movePos] - 1000 : -1;
         }
         else {
           int num = mv[movePos] % 4 + 1;
           int mode = mv[movePos] / 4 % 6;
           boolean clockwise = num < 3;
           if (num == 4)
             num = 2;
           if (moveDir < 0) {
             clockwise = !clockwise;
             num = 4 - num;
           }
           spin(mv[movePos] / 24, num, mode, clockwise, moveAnimated);
           if (moveOne)
             restart = true;
         }
         if (moveDir > 0) {
           movePos++;
           if (movePos < mv.length && mv[movePos] >= 1000) {
             curInfoText = mv[movePos] - 1000;
             movePos++;
           }
           if (movePos == mv.length) {
             if (!demo)
               break;
             movePos = 0;
             initInfoText(mv);
             for (int i = 0; i < 6; i++)
               for (int j = 0; j < 9; j++)
                 cube[i][j] = initialCube[i][j];
           }
         }
         else
           curInfoText = -1;
         if (interrupted || restarted || restart)
           break;
       }
       animating = false;
       drawButtons = true;
       repaint();
       if (demo) {
         clear();
         demo = false;
       }
     } while (!interrupted);
   }
   //System.err.println("Interrupted!");
 } // run()
 private void sleep(int time) {
   synchronized (animThread) {
     try {
       animThread.wait(time);
     }
     catch (InterruptedException e) {
       interrupted = true;
     }
   }
 }
 private void clear() {
   synchronized (animThread) {
     movePos = 0;
     if (move.length > 0)
       initInfoText(move[curMove]);
     natural = true;
     mirrored = false;
     for (int i = 0; i < 6; i++)
       for (int j = 0; j < 9; j++)
         cube[i][j] = initialCube[i][j];
     for (int i = 0; i < 3; i++) {
       eye[i] = initialEye[i];
       eyeX[i] = initialEyeX[i];
       eyeY[i] = initialEyeY[i];
     }
   }
 }
 private void spin(int layer, int num, int mode, boolean clockwise, boolean animated) {
   twisting = false;
   natural = true;
   spinning = true;
   originalAngle = 0;
   if (faceTwistDirs[layer] > 0)
     clockwise = !clockwise;
   if (animated) {
     double phit = Math.PI / 2; // target for currentAngle (default pi/2)
     double phis = clockwise ? 1.0 : -1.0; // sign
     int turnTime = 67 * speed; // milliseconds to be used for one turn
     if (num == 2) {
       phit = Math.PI;
       turnTime = 67 * doubleSpeed; // double turn is usually faster than two quarter turns
     }
     twisting = true;
     twistedLayer = layer;
     twistedMode = mode;
     splitCube(layer); // start twisting
     long sTime = System.currentTimeMillis();
     long lTime = sTime;
     double d = phis * phit / turnTime;
     for (currentAngle = 0; currentAngle * phis < phit; currentAngle = d * (lTime - sTime)) {
       repaint();
       sleep(25);
       if (interrupted || restarted)
         break;
       lTime = System.currentTimeMillis();
     }
   }
   currentAngle = 0;
   twisting = false;
   natural = true;
   twistLayers(cube, layer, num, mode);
   spinning = false;
   if (animated)
     repaint();
 }
 // cube dimensions in number of facelets (mincol, maxcol, minrow, maxrow) for compact cube
 private static final int[][][] cubeBlocks = {
   {{0, 3}, {0, 3}}, // U
   {{0, 3}, {0, 3}}, // D
   {{0, 3}, {0, 3}}, // F
   {{0, 3}, {0, 3}}, // B
   {{0, 3}, {0, 3}}, // L
   {{0, 3}, {0, 3}}  // R
 };
 // subcube dimensions
 private final int[][][] topBlocks = new int[6][][];
 private final int[][][] midBlocks = new int[6][][];
 private final int[][][] botBlocks = new int[6][][];
 // all possible subcube dimensions for top and bottom layers
 private static final int[][][] topBlockTable = {
   {{0, 0}, {0, 0}},
   {{0, 3}, {0, 3}},
   {{0, 3}, {0, 1}},
   {{0, 1}, {0, 3}},
   {{0, 3}, {2, 3}},
   {{2, 3}, {0, 3}}
 };
 // subcube dimmensions for middle layers
 private static final int[][][] midBlockTable = {
   {{0, 0}, {0, 0}},
   {{0, 3}, {1, 2}},
   {{1, 2}, {0, 3}}
 };
 // indices to topBlockTable[] and botBlockTable[] for each twistedLayer value
 private static final int[][] topBlockFaceDim = {
 // U  D  F  B  L  R
   {1, 0, 3, 3, 2, 3}, // U
   {0, 1, 5, 5, 4, 5}, // D
   {2, 3, 1, 0, 3, 2}, // F
   {4, 5, 0, 1, 5, 4}, // B
   {3, 2, 2, 4, 1, 0}, // L
   {5, 4, 4, 2, 0, 1}  // R
 };
 private static final int[][] midBlockFaceDim = {
 // U  D  F  B  L  R
   {0, 0, 2, 2, 1, 2}, // U
   {0, 0, 2, 2, 1, 2}, // D
   {1, 2, 0, 0, 2, 1}, // F
   {1, 2, 0, 0, 2, 1}, // B
   {2, 1, 1, 1, 0, 0}, // L
   {2, 1, 1, 1, 0, 0}  // R
 };
 private static final int[][] botBlockFaceDim = {
 // U  D  F  B  L  R
   {0, 1, 5, 5, 4, 5}, // U
   {1, 0, 3, 3, 2, 3}, // D
   {4, 5, 0, 1, 5, 4}, // F
   {2, 3, 1, 0, 3, 2}, // B
   {5, 4, 4, 2, 0, 1}, // L
   {3, 2, 2, 4, 1, 0}  // R
 };
 private void splitCube(int layer) {
   for (int i = 0; i < 6; i++) { // for all faces
     topBlocks[i] = topBlockTable[topBlockFaceDim[layer][i]];
     botBlocks[i] = topBlockTable[botBlockFaceDim[layer][i]];
     midBlocks[i] = midBlockTable[midBlockFaceDim[layer][i]];
   }
   natural = false;
 }
 private void twistLayers(int[][] cube, int layer, int num, int mode) {
   switch (mode) {
    case 3:
     twistLayer(cube, layer ^ 1, num, false);
    case 2:
     twistLayer(cube, layer, 4 - num, false);
    case 1:
     twistLayer(cube, layer, 4 - num, true);
     break;
    case 5:
     twistLayer(cube, layer ^ 1, 4 - num, false);
     twistLayer(cube, layer, 4 - num, false);
     break;
    case 4:
     twistLayer(cube, layer ^ 1, num, false);
    default:
     twistLayer(cube, layer, 4 - num, false);
   }
 }
 // top facelet cycle
 private static final int[] cycleOrder = {0, 1, 2, 5, 8, 7, 6, 3};
 // side facelet cycle offsets
 private static final int[] cycleFactors = {1, 3, -1, -3, 1, 3, -1, -3};
 private static final int[] cycleOffsets = {0, 2,  8,  6, 3, 1,  5,  7};
 // indices for faces of layers
 private static final int[][] cycleLayerSides = {
   {3, 3, 3, 0}, // U: F=6-3k R=6-3k B=6-3k L=k
   {2, 1, 1, 1}, // D: L=8-k  B=2+3k R=2+3k F=2+3k
   {3, 3, 0, 0}, // F: L=6-3k D=6-3k R=k    U=k
   {2, 1, 1, 2}, // B: R=8-k  D=2+3k L=2+3k U=8-k
   {3, 2, 0, 0}, // L: U=6-3k B=8-k  D=k    F=k
   {2, 2, 0, 1}  // R: F=8-k  D=8-k  B=k    U=2+3k
 };
 // indices for sides of center layers
 private static final int[][] cycleCenters = {
   {7, 7, 7, 4}, // E'(U): F=7-3k R=7-3k B=7-3k L=3+k
   {6, 5, 5, 5}, // E (D): L=5-k  B=1+3k R=1+3k F=1+3k
   {7, 7, 4, 4}, // S (F): L=7-3k D=7-3k R=3+k  U=3+k
   {6, 5, 5, 6}, // S'(B): R=5-k  D=1+3k L=1+3k U=5-k
   {7, 6, 4, 4}, // M (L): U=7-3k B=8-k  D=3+k  F=3+k
   {6, 6, 4, 5}  // M'(R): F=5-k  D=5-k  B=3+k  U=1+3k
 };
 private final int[] twistBuffer = new int[12];
 private void twistLayer(int[][] cube, int layer, int num, boolean middle) {
   if (!middle) {
     // rotate top facelets
     for (int i = 0; i < 8; i++) // to buffer
       twistBuffer[(i + num * 2) % 8] = cube[layer][cycleOrder[i]];
     for (int i = 0; i < 8; i++) // to cube
       cube[layer][cycleOrder[i]] = twistBuffer[i];
   }
   // rotate side facelets
   int k = num * 3;
   for (int i = 0; i < 4; i++) { // to buffer
     int n = adjacentFaces[layer][i];
     int c = middle ? cycleCenters[layer][i] : cycleLayerSides[layer][i];
     int factor = cycleFactors[c];
     int offset = cycleOffsets[c];
     for (int j = 0; j < 3; j++) {
       twistBuffer[k % 12] = cube[n][j * factor + offset];
       k++;
     }
   }
   k = 0; // MS VM JIT bug if placed into the loop init
   for (int i = 0; i < 4; i++) { // to cube
     int n = adjacentFaces[layer][i];
     int c = middle ? cycleCenters[layer][i] : cycleLayerSides[layer][i];
     int factor = cycleFactors[c];
     int offset = cycleOffsets[c];
     int j = 0; // MS VM JIT bug if for is used
     while (j < 3) {
       cube[n][j * factor + offset] = twistBuffer[k];
       j++;
       k++;
     }
   }
 }
 // double buffered animation
 private Graphics graphics = null;
 private Image image = null;
 // cube window size (applet window is resizable)
 private int width;
 private int height;
 // last position of mouse (for dragging the cube)
 private int lastX;
 private int lastY;
 // last position of mouse (when waiting for clear decission)
 private int lastDragX;
 private int lastDragY;
 // drag areas
 private int dragAreas;
 private final int[][] dragCornersX = new int[18][4];
 private final int[][] dragCornersY = new int[18][4];
 private final double[] dragDirsX = new double[18];
 private final double[] dragDirsY = new double[18];
 private static final int[][][] dragBlocks = {
   {{0, 0}, {3, 0}, {3, 1}, {0, 1}},
   {{3, 0}, {3, 3}, {2, 3}, {2, 0}},
   {{3, 3}, {0, 3}, {0, 2}, {3, 2}},
   {{0, 3}, {0, 0}, {1, 0}, {1, 3}},
   // center slices
   {{0, 1}, {3, 1}, {3, 2}, {0, 2}},
   {{2, 0}, {2, 3}, {1, 3}, {1, 0}}
 };
 private static final int[][] areaDirs = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 0}, {0, 1}};
 private static final int[][] twistDirs = {
   { 1,  1,  1,  1,  1, -1}, // U
   { 1,  1,  1,  1,  1, -1}, // D
   { 1, -1,  1, -1,  1,  1}, // F
   { 1, -1,  1, -1, -1,  1}, // B
   {-1,  1, -1,  1, -1, -1}, // L
   { 1, -1,  1, -1,  1,  1}  // R
 };
 private int[] dragLayers = new int[18]; // which layers belongs to dragCorners
 private int[] dragModes = new int[18]; // which layer modes dragCorners
 // current drag directions
 private double dragX;
 private double dragY;
 // various sign tables for computation of directions of rotations
 private static final int[][][] rotCos = {
   {{ 1,  0,  0}, { 0,  0,  0}, { 0,  0,  1}}, // U-D
   {{ 1,  0,  0}, { 0,  1,  0}, { 0,  0,  0}}, // F-B
   {{ 0,  0,  0}, { 0,  1,  0}, { 0,  0,  1}}  // L-R
 };
 private static final int[][][] rotSin = {
   {{ 0,  0,  1}, { 0,  0,  0}, {-1,  0,  0}}, // U-D
   {{ 0,  1,  0}, {-1,  0,  0}, { 0,  0,  0}}, // F-B
   {{ 0,  0,  0}, { 0,  0,  1}, { 0, -1,  0}}  // L-R
 };
 private static final int[][][] rotVec = {
   {{ 0,  0,  0}, { 0,  1,  0}, { 0,  0,  0}}, // U-D
   {{ 0,  0,  0}, { 0,  0,  0}, { 0,  0,  1}}, // F-B
   {{ 1,  0,  0}, { 0,  0,  0}, { 0,  0,  0}}  // L-R
 };
 private static final int[] rotSign = {1, -1, 1, -1, 1, -1}; // U, D, F, B, L, R
 // temporary eye vectors for twisted sub-cube rotation
 private final double[] tempEye = new double[3];
 private final double[] tempEyeX = new double[3];
 private final double[] tempEyeY = new double[3];
 // temporary eye vectors for second twisted sub-cube rotation (antislice)
 private final double[] tempEye2 = new double[3];
 private final double[] tempEyeX2 = new double[3];
 private final double[] tempEyeY2 = new double[3];
 // temporary vectors to compute visibility in perspective projection
 private final double[] perspEye = new double[3];
 private final double[] perspEyeI = new double[3];
 private final double[] perspNormal = new double[3];
 // eye arrays to store various eyes for various modes
 private final double[][] eyeArray = new double[3][];
 private final double[][] eyeArrayX = new double[3][];
 private final double[][] eyeArrayY = new double[3][];
 private final int[][] eyeOrder = {{1, 0, 0}, {0, 1, 0}, {1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 2}};
 private final int[][][][] blockArray = new int[3][][][];
 private final int[][] blockMode = {{0, 2, 2}, {2, 1, 2}, {2, 2, 2}, {2, 2, 2}, {2, 2, 2}, {2, 2, 2}};
 private final int[][] drawOrder = {{0, 1, 2}, {2, 1, 0}, {0, 2, 1}};
 public void paint(Graphics g) {
   Dimension size = getSize(); // inefficient - Java 1.1
   // create offscreen buffer for double buffering
   if (image == null || size.width != width || size.height - buttonHeight != height) {
     width = size.width;
     height = size.height;
     image = createImage(width, height);
     graphics = image.getGraphics();
     textHeight = graphics.getFontMetrics().getHeight() - graphics.getFontMetrics().getLeading();
     if (buttonBar == 1)
       height -= buttonHeight;
     drawButtons = true;
   }
   graphics.setColor(bgColor);
   graphics.setClip(0, 0, width, height);
   graphics.fillRect(0, 0, width, height);
   synchronized (animThread) {
     dragAreas = 0;
     if (natural) // compact cube
       fixBlock(eye, eyeX, eyeY, cubeBlocks, 3); // draw cube and fill drag areas
     else { // in twisted state
       // compute top observer
       double cosA = Math.cos(originalAngle + currentAngle);
       double sinA = Math.sin(originalAngle + currentAngle) * rotSign[twistedLayer];
       for (int i = 0; i < 3; i++) {
         tempEye[i] = 0;
         tempEyeX[i] = 0;
         for (int j = 0; j < 3; j++) {
           int axis = twistedLayer / 2;
           tempEye[i] += eye[j] * (rotVec[axis][i][j] + rotCos[axis][i][j] * cosA + rotSin[axis][i][j] * sinA);
           tempEyeX[i] += eyeX[j] * (rotVec[axis][i][j] + rotCos[axis][i][j] * cosA + rotSin[axis][i][j] * sinA);
         }
       }
       vMul(tempEyeY, tempEye, tempEyeX);
       // compute bottom anti-observer
       double cosB = Math.cos(originalAngle - currentAngle);
       double sinB = Math.sin(originalAngle - currentAngle) * rotSign[twistedLayer];
       for (int i = 0; i < 3; i++) {
         tempEye2[i] = 0;
         tempEyeX2[i] = 0;
         for (int j = 0; j < 3; j++) {
           int axis = twistedLayer / 2;
           tempEye2[i] += eye[j] * (rotVec[axis][i][j] + rotCos[axis][i][j] * cosB + rotSin[axis][i][j] * sinB);
           tempEyeX2[i] += eyeX[j] * (rotVec[axis][i][j] + rotCos[axis][i][j] * cosB + rotSin[axis][i][j] * sinB);
         }
       }
       vMul(tempEyeY2, tempEye2, tempEyeX2);
       eyeArray[0] = eye;
       eyeArrayX[0] = eyeX;
       eyeArrayY[0] = eyeY;
       eyeArray[1] = tempEye;
       eyeArrayX[1] = tempEyeX;
       eyeArrayY[1] = tempEyeY;
       eyeArray[2] = tempEye2;
       eyeArrayX[2] = tempEyeX2;
       eyeArrayY[2] = tempEyeY2;
       blockArray[0] = topBlocks;
       blockArray[1] = midBlocks;
       blockArray[2] = botBlocks;
       // perspective corrections
       vSub(vScale(vCopy(perspEye, eye), 5.0 + persp), vScale(vCopy(perspNormal, faceNormals[twistedLayer]), 1.0 / 3.0));
       vSub(vScale(vCopy(perspEyeI, eye), 5.0 + persp), vScale(vCopy(perspNormal, faceNormals[twistedLayer ^ 1]), 1.0 / 3.0));
       double topProd = vProd(perspEye, faceNormals[twistedLayer]);
       double botProd = vProd(perspEyeI, faceNormals[twistedLayer ^ 1]);
       int orderMode;
       if (topProd < 0 && botProd > 0) // top facing away
         orderMode = 0;
       else if (topProd > 0 && botProd < 0) // bottom facing away: draw it first
         orderMode = 1;
       else // both top and bottom layer facing away: draw them first
         orderMode = 2;
       fixBlock(eyeArray[eyeOrder[twistedMode][drawOrder[orderMode][0]]],
                eyeArrayX[eyeOrder[twistedMode][drawOrder[orderMode][0]]],
                eyeArrayY[eyeOrder[twistedMode][drawOrder[orderMode][0]]],
                blockArray[drawOrder[orderMode][0]],
                blockMode[twistedMode][drawOrder[orderMode][0]]);
       fixBlock(eyeArray[eyeOrder[twistedMode][drawOrder[orderMode][1]]],
                eyeArrayX[eyeOrder[twistedMode][drawOrder[orderMode][1]]],
                eyeArrayY[eyeOrder[twistedMode][drawOrder[orderMode][1]]],
                blockArray[drawOrder[orderMode][1]],
                blockMode[twistedMode][drawOrder[orderMode][1]]);
       fixBlock(eyeArray[eyeOrder[twistedMode][drawOrder[orderMode][2]]],
                eyeArrayX[eyeOrder[twistedMode][drawOrder[orderMode][2]]],
                eyeArrayY[eyeOrder[twistedMode][drawOrder[orderMode][2]]],
                blockArray[drawOrder[orderMode][2]],
                blockMode[twistedMode][drawOrder[orderMode][2]]);
     }
     if (!pushed && !animating) // no button should be deceased
       buttonPressed = -1;
     if (!demo && move.length > 0) {
       if (move[curMove].length > 0) { // some turns
         graphics.setColor(Color.black);
         graphics.drawRect(0, height - progressHeight, width - 1, progressHeight - 1);
         graphics.setColor(textColor);
         int progress = (width - 2) * realMovePos(move[curMove], movePos) / realMoveLength(move[curMove]);
         graphics.fillRect(1, height - progressHeight + 1, progress, progressHeight - 2);
         graphics.setColor(bgColor.darker());
         graphics.fillRect(1 + progress, height - progressHeight + 1, width - 2 - progress, progressHeight - 2);
         String s = "" + moveLength(move[curMove], movePos) + "/" + moveLength(move[curMove], -1) + metricChar[metric];
         int w = graphics.getFontMetrics().stringWidth(s);
         int x = width - w - 2;
         int y = height - progressHeight - 2; //int base = graphics.getFontMetrics().getDescent();
         if (moveText > 0 && textHeight > 0) {
           drawString(graphics, s, x, y - textHeight);
           drawMoveText(graphics, y);
         }
         else
           drawString(graphics, s, x, y);
       }
       if (move.length > 1) { // more sequences
         graphics.setClip(0, 0, width, height);
         int b = graphics.getFontMetrics().getDescent();
         int y = textHeight - b;
         String s = "" + (curMove + 1) + "/" + move.length;
         int w = graphics.getFontMetrics().stringWidth(s);
         int x = width - w - buttonHeight - 2;
         drawString(graphics, s, x, y);
         // draw button
         graphics.setColor(buttonBgColor);
         graphics.fill3DRect(width - buttonHeight, 0, buttonHeight, buttonHeight, buttonPressed != 7);
         drawButton(graphics, 7, width - buttonHeight / 2, buttonHeight / 2);
       }
     }
     if (curInfoText >= 0) {
       graphics.setClip(0, 0, width, height);
       int b = graphics.getFontMetrics().getDescent();
       int y = textHeight - b;
       drawString(graphics, infoText[curInfoText], 0, y);
     }
     if (drawButtons && buttonBar != 0) // omit unneccessary redrawing
       drawButtons(graphics);
   }
   g.drawImage(image, 0, 0, this);
 } // paint()
 public void update(Graphics g) {
   paint(g);
 }
 // polygon co-ordinates to fill (cube faces or facelets)
 private final int[] fillX = new int[4];
 private final int[] fillY = new int[4];
 // projected vertex co-ordinates (to screen)
 private final double[] coordsX = new double[8];
 private final double[] coordsY = new double[8];
 private final double[][] cooX = new double[6][4];
 private final double[][] cooY = new double[6][4];
 private static final double[][] border = {{0.10, 0.10}, {0.90, 0.10}, {0.90, 0.90}, {0.10, 0.90}};
 private static final int[][] factors = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
 private final double[] faceShiftX = new double[6];
 private final double[] faceShiftY = new double[6];
 private final double[] tempNormal = new double[3];
 private void fixBlock(double[] eye, double[] eyeX, double[] eyeY, int[][][] blocks, int mode) {
   // project 3D co-ordinates into 2D screen ones
   for (int i = 0; i < 8; i++) {
     double min = width < height ? width : height - progressHeight;
     double x = min / 3.7 * vProd(cornerCoords[i], eyeX) * scale;
     double y = min / 3.7 * vProd(cornerCoords[i], eyeY) * scale;
     double z = min / (5.0 + persp) * vProd(cornerCoords[i], eye) * scale;
     x = x / (1 - z / min); // perspective transformation
     y = y / (1 - z / min); // perspective transformation
     coordsX[i] = width / 2.0 + x;
     if (align == 0)
       coordsY[i] = (height - progressHeight) / 2.0 * scale  - y;
     else if (align == 2)
       coordsY[i] = height - progressHeight - (height - progressHeight) / 2.0 * scale  - y;
     else
       coordsY[i] = (height - progressHeight) / 2.0 - y;
   }
   // setup corner co-ordinates for all faces
   for (int i = 0; i < 6; i++) { // all faces
     for (int j = 0; j < 4; j++) { // all face corners
       cooX[i][j] = coordsX[faceCorners[i][j]];
       cooY[i][j] = coordsY[faceCorners[i][j]];
     }
   }
   if (hint) { // draw hint hiden facelets
     for (int i = 0; i < 6; i++) { // all faces
       vSub(vScale(vCopy(perspEye, eye), 5.0 + persp), faceNormals[i]); // perspective correction
       if (vProd(perspEye, faceNormals[i]) < 0) { // draw only hiden faces
         vScale(vCopy(tempNormal, faceNormals[i]), faceShift);
         double min = width < height ? width : height - progressHeight;
         double x = min / 3.7 * vProd(tempNormal, eyeX);
         double y = min / 3.7 * vProd(tempNormal, eyeY);
         double z = min / (5.0 + persp) * vProd(tempNormal, eye);
         x = x / (1 - z / min); // perspective transformation
         y = y / (1 - z / min); // perspective transformation
         int sideW = blocks[i][0][1] - blocks[i][0][0];
         int sideH = blocks[i][1][1] - blocks[i][1][0];
         if (sideW > 0 && sideH > 0) { // this side is not only black
           // draw colored facelets
           for (int n = 0, p = blocks[i][1][0]; n < sideH; n++, p++) {
             for (int o = 0, q = blocks[i][0][0]; o < sideW; o++, q++) {
               for (int j = 0; j < 4; j++) {
                 getCorners(i, j, fillX, fillY, q + border[j][0], p + border[j][1], mirrored);
                 fillX[j] += mirrored ? -x : x;
                 fillY[j] -= y;
               }
               graphics.setColor(colors[cube[i][p * 3 + q]]);
               graphics.fillPolygon(fillX, fillY, 4);
               graphics.setColor(colors[cube[i][p * 3 + q]].darker());
               graphics.drawPolygon(fillX, fillY, 4);
             }
           }
         }
       }
     }
   }
   // draw black antialias
   for (int i = 0; i < 6; i++) { // all faces
     int sideW = blocks[i][0][1] - blocks[i][0][0];
     int sideH = blocks[i][1][1] - blocks[i][1][0];
     if (sideW > 0 && sideH > 0) {
       for (int j = 0; j < 4; j++) // corner co-ordinates
         getCorners(i, j, fillX, fillY, blocks[i][0][factors[j][0]], blocks[i][1][factors[j][1]], mirrored);
       if (sideW == 3 && sideH == 3)
         graphics.setColor(bgColor2);
       else
         graphics.setColor(Color.black);
       graphics.drawPolygon(fillX, fillY, 4);
     }
   }
   // find and draw black inner faces
   for (int i = 0; i < 6; i++) { // all faces
     int sideW = blocks[i][0][1] - blocks[i][0][0];
     int sideH = blocks[i][1][1] - blocks[i][1][0];
     if (sideW <= 0 || sideH <= 0) { // this face is inner and only black
       for (int j = 0; j < 4; j++) { // for all corners
         int k = oppositeCorners[i][j];
         fillX[j] = (int)(cooX[i][j] + (cooX[i ^ 1][k] - cooX[i][j]) * 2.0 / 3.0);
         fillY[j] = (int)(cooY[i][j] + (cooY[i ^ 1][k] - cooY[i][j]) * 2.0 / 3.0);
         if (mirrored)
           fillX[j] = width - fillX[j];
       }
       graphics.setColor(Color.black);
       graphics.fillPolygon(fillX, fillY, 4);
     }
     else {
       // draw black face background (do not care about normals and visibility!)
       for (int j = 0; j < 4; j++) // corner co-ordinates
         getCorners(i, j, fillX, fillY, blocks[i][0][factors[j][0]], blocks[i][1][factors[j][1]], mirrored);
       graphics.setColor(Color.black);
       graphics.fillPolygon(fillX, fillY, 4);
     }
   }
   // draw all visible faces and get dragging regions
   for (int i = 0; i < 6; i++) { // all faces
     vSub(vScale(vCopy(perspEye, eye), 5.0 + persp), faceNormals[i]); // perspective correction
     if (vProd(perspEye, faceNormals[i]) > 0) { // draw only faces towards us
       int sideW = blocks[i][0][1] - blocks[i][0][0];
       int sideH = blocks[i][1][1] - blocks[i][1][0];
       if (sideW > 0 && sideH > 0) { // this side is not only black
         // draw colored facelets
         for (int n = 0, p = blocks[i][1][0]; n < sideH; n++, p++) {
           for (int o = 0, q = blocks[i][0][0]; o < sideW; o++, q++) {
             for (int j = 0; j < 4; j++)
               getCorners(i, j, fillX, fillY, q + border[j][0], p + border[j][1], mirrored);
             graphics.setColor(colors[cube[i][p * 3 + q]].darker());
             graphics.drawPolygon(fillX, fillY, 4);
             graphics.setColor(colors[cube[i][p * 3 + q]]);
             graphics.fillPolygon(fillX, fillY, 4);
           }
         }
       }
       if (!editable || animating) // no need of twisting while animating
         continue;
       // horizontal and vertical directions of face - interpolated
       double dxh = (cooX[i][1] - cooX[i][0] + cooX[i][2] - cooX[i][3]) / 6.0;
       double dyh = (cooX[i][3] - cooX[i][0] + cooX[i][2] - cooX[i][1]) / 6.0;
       double dxv = (cooY[i][1] - cooY[i][0] + cooY[i][2] - cooY[i][3]) / 6.0;
       double dyv = (cooY[i][3] - cooY[i][0] + cooY[i][2] - cooY[i][1]) / 6.0;
       if (mode == 3) { // just the normal cube
         for (int j = 0; j < 6; j++) { // 4 areas 3x1 per face + 2 center slices
           for (int k = 0; k < 4; k++) // 4 points per area
             getCorners(i, k, dragCornersX[dragAreas], dragCornersY[dragAreas],
                              dragBlocks[j][k][0], dragBlocks[j][k][1], false);
           dragDirsX[dragAreas] = (dxh * areaDirs[j][0] + dxv * areaDirs[j][1]) * twistDirs[i][j];
           dragDirsY[dragAreas] = (dyh * areaDirs[j][0] + dyv * areaDirs[j][1]) * twistDirs[i][j];
           dragLayers[dragAreas] = adjacentFaces[i][j % 4];
           if (j >= 4)
             dragLayers[dragAreas] &= ~1;
           dragModes[dragAreas] = j / 4;
           dragAreas++;
           if (dragAreas == 18)
             break;
         }
       }
       else if (mode == 0) { // twistable top layer
         if (i != twistedLayer && sideW > 0 && sideH > 0) { // only 3x1 faces
           int j = sideW == 3 ? (blocks[i][1][0] == 0 ? 0 : 2) : (blocks[i][0][0] == 0 ? 3 : 1);
           for (int k = 0; k < 4; k++)
             getCorners(i, k, dragCornersX[dragAreas], dragCornersY[dragAreas],
                              dragBlocks[j][k][0], dragBlocks[j][k][1], false);
           dragDirsX[dragAreas] = (dxh * areaDirs[j][0] + dxv * areaDirs[j][1]) * twistDirs[i][j];
           dragDirsY[dragAreas] = (dyh * areaDirs[j][0] + dyv * areaDirs[j][1]) * twistDirs[i][j];
           dragLayers[dragAreas] = twistedLayer;
           dragModes[dragAreas] = 0;
           dragAreas++;
         }
       }
       else if (mode == 1) { // twistable center layer
         if (i != twistedLayer && sideW > 0 && sideH > 0) { // only 3x1 faces
           int j = sideW == 3 ? 4 : 5;
           for (int k = 0; k < 4; k++)
             getCorners(i, k, dragCornersX[dragAreas], dragCornersY[dragAreas],
                              dragBlocks[j][k][0], dragBlocks[j][k][1], false);
           dragDirsX[dragAreas] = (dxh * areaDirs[j][0] + dxv * areaDirs[j][1]) * twistDirs[i][j];
           dragDirsY[dragAreas] = (dyh * areaDirs[j][0] + dyv * areaDirs[j][1]) * twistDirs[i][j];
           dragLayers[dragAreas] = twistedLayer;
           dragModes[dragAreas] = 1;
           dragAreas++;
         }
       }
     }
   }
 }
 private void getCorners(int face, int corner, int[] cornersX, int[] cornersY, double factor1, double factor2, boolean mirror) {
   factor1 /= 3.0;
   factor2 /= 3.0;
   double x1 = cooX[face][0] + (cooX[face][1] - cooX[face][0]) * factor1;
   double y1 = cooY[face][0] + (cooY[face][1] - cooY[face][0]) * factor1;
   double x2 = cooX[face][3] + (cooX[face][2] - cooX[face][3]) * factor1;
   double y2 = cooY[face][3] + (cooY[face][2] - cooY[face][3]) * factor1;
   cornersX[corner] = (int)(0.5 + x1 + (x2 - x1) * factor2);
   cornersY[corner] = (int)(0.5 + y1 + (y2 - y1) * factor2);
   if (mirror)
     cornersX[corner] = width - cornersX[corner];
 }
 private void drawButtons(Graphics g) {
   if (buttonBar == 2) { // only clear (rewind) button
     g.setColor(buttonBgColor);
     g.fill3DRect(0, height - buttonHeight, buttonHeight, buttonHeight, buttonPressed != 0);
     drawButton(g, 0, buttonHeight / 2, height - (buttonHeight + 1) / 2);
     return;
   }
   if (buttonBar == 1) { // full buttonbar
     g.setClip(0, height, width, buttonHeight);
     int buttonX = 0;
     for (int i = 0; i < 7; i++) {
       int buttonWidth = (width - buttonX) / (7 - i);
       g.setColor(buttonBgColor);
       g.fill3DRect(buttonX, height, buttonWidth, buttonHeight, buttonPressed != i);
       drawButton(g, i, buttonX + buttonWidth / 2, height + buttonHeight / 2);
       buttonX += buttonWidth;
     }
     drawButtons = false;
     return;
   }
 }
 private void drawButton(Graphics g, int i, int x, int y) {
   g.setColor(Color.white);
   switch (i) {
    case 0: // rewind
     drawRect(g, x - 4, y - 3, 3, 7);
     drawArrow(g, x + 3, y, -1); // left
     break;
    case 1: // reverse step
     drawRect(g, x + 2, y - 3, 3, 7);
     drawArrow(g, x, y, -1); // left
     break;
    case 2: // reverse play
     drawArrow(g, x + 2, y, -1); // left
     break;
    case 3: // stop / mirror
     if (animating)
       drawRect(g, x - 3, y - 3, 7, 7);
     else {
       drawRect(g, x - 3, y - 2, 7, 5);
       drawRect(g, x - 1, y - 4, 3, 9);
     }
     break;
    case 4: // play
     drawArrow(g, x - 2, y, 1); // right
     break;
    case 5: // step
     drawRect(g, x - 4, y - 3, 3, 7);
     drawArrow(g, x, y, 1); // right
     break;
    case 6: // fast forward
     drawRect(g, x + 1, y - 3, 3, 7);
     drawArrow(g, x - 4, y, 1); // right
     break;
    case 7: // next sequence
     drawArrow(g, x - 2, y, 1); // right
     break;
   }
 }
 private static void drawArrow(Graphics g, int x, int y, int dir) {
   g.setColor(Color.black);
   g.drawLine(x, y - 3, x, y + 3);
   x += dir;
   for (int i = 0; i >= -3 && i <= 3; i += dir) {
     int j = 3 - i * dir;
     g.drawLine(x + i, y + j, x + i, y - j);
   }
   g.setColor(Color.white);
   for (int i = 0; i >= -1 && i <= 1; i += dir) {
     int j = 1 - i * dir;
     g.drawLine(x + i, y + j, x + i, y - j);
   }
 }
 private static void drawRect(Graphics g, int x, int y, int width, int height) {
   g.setColor(Color.black);
   g.drawRect(x, y, width - 1, height - 1);
   g.setColor(Color.white);
   g.fillRect(x + 1, y + 1, width - 2, height - 2);
 }
 private static final int[] textOffset = {1, 1, -1, -1, -1, 1, 1, -1, -1, 0, 1, 0, 0, 1, 0, -1};
 private void drawString(Graphics g, String s, int x, int y) {
   if (outlined) {
     g.setColor(Color.black);
     for (int i = 0; i < textOffset.length; i += 2)
       g.drawString(s, x + textOffset[i], y + textOffset[i + 1]);
     g.setColor(Color.white);
   }
   else
     g.setColor(textColor);
   g.drawString(s, x, y);
 }
 private void drawMoveText(Graphics g, int y) {
   g.setClip(0, height - progressHeight - textHeight, width, textHeight);
   g.setColor(Color.black);
   int pos = movePos == 0 ? arrayMovePos(move[curMove], movePos) : movePos;
   String s1 = moveText(move[curMove], 0, pos);
   String s2 = turnText(move[curMove], pos);
   String s3 = moveText(move[curMove], pos + 1, move[curMove].length);
   int w1 = g.getFontMetrics().stringWidth(s1);
   int w2 = g.getFontMetrics().stringWidth(s2);
   int w3 = g.getFontMetrics().stringWidth(s3);
   int x = 1;
   if (x + w1 + w2 + w3 > width) {
     x = Math.min(1, width / 2 - w1 - w2 / 2);
     x = Math.max(x, width - w1 - w2 - w3 - 2);
   }
   if (w2 > 0) {
     g.setColor(hlColor);
     g.fillRect(x + w1 - 1, height - progressHeight - textHeight, w2 + 2, textHeight);
   }
   if (w1 > 0)
     drawString(g, s1, x, y);
   if (w2 > 0)
     drawString(g, s2, x + w1, y);
   if (w3 > 0)
     drawString(g, s3, x + w1 + w2, y);
 }
 private int selectButton(int x, int y) {
   if (buttonBar == 0)
     return -1;
   if (move.length > 1 && x >= width - buttonHeight && x < width && y >= 0 && y < buttonHeight)
     return 7;
   if (buttonBar == 2) { // only clear (rewind) button present
     if (x >= 0 && x < buttonHeight && y >= height - buttonHeight && y < height)
       return 0;
     return -1;
   }
   if (y < height)
     return -1;
   int buttonX = 0;
   for (int i = 0; i < 7; i++) {
     int buttonWidth = (width - buttonX) / (7 - i);
     if (x >= buttonX && x < buttonX + buttonWidth && y >= height && y < height + buttonHeight)
       return i;
     buttonX += buttonWidth;
   }
   return -1;
 }
 // Mouse event handlers
 private final static int[] buttonAction = {-1, 3, 1, -1, 0, 2, 4, -1};
 public void mousePressed(MouseEvent e) {
   lastDragX = lastX = e.getX();
   lastDragY = lastY = e.getY();
   toTwist = false;
   buttonPressed = selectButton(lastX, lastY);
   if (buttonPressed >= 0) {
     pushed = true;
     if (buttonPressed == 3) {
       if (!animating) // special feature
         mirrored = !mirrored;
       else
         stopAnimation();
     }
     else if (buttonPressed == 0) { // clear everything to the initial setup
       stopAnimation();
       clear();
     }
     else if (buttonPressed == 7) { // next sequence
       stopAnimation();
       clear();
       curMove = curMove < move.length - 1 ? curMove + 1 : 0;
     }
     else
       startAnimation(buttonAction[buttonPressed]);
     drawButtons = true;
     repaint();
   }
   else if (progressHeight > 0 && move.length > 0 && move[curMove].length > 0 && lastY >= height - progressHeight && lastY < height) {
     stopAnimation();
     int len = realMoveLength(move[curMove]);
     int pos = ((lastX - 1) * len * 2 / (width - 2) + 1) / 2;
     pos = Math.max(0, Math.min(len, pos));
     if (pos > 0)
       pos = arrayMovePos(move[curMove], pos);
     if (pos > movePos)
       doMove(cube, move[curMove], movePos, pos - movePos, false);
     if (pos < movePos)
       doMove(cube, move[curMove], pos, movePos - pos, true);
     movePos = pos;
     dragging = true;
     repaint();
   }
   else {
     if (mirrored)
       lastDragX = lastX = width - lastX;
     if (editable && !animating &&
      (e.getModifiers() & InputEvent.BUTTON1_MASK) != 0 &&
      (e.getModifiers() & InputEvent.SHIFT_MASK) == 0)
       toTwist = true;
   }
 }
 public void mouseReleased(MouseEvent e) {
   dragging = false;
   if (pushed) {
     pushed = false;
     drawButtons = true;
     repaint();
   }
   else if (twisting && !spinning) {
     twisting = false;
     originalAngle += currentAngle;
     currentAngle = 0.0;
     double angle = originalAngle;
     while (angle < 0.0)
       angle += 32.0 * Math.PI;
     int num = (int)(angle * 8.0 / Math.PI) % 16; // 2pi ~ 16
     if (num % 4 == 0 || num % 4 == 3) { // close enough to a corner
       num = (num + 1) / 4; // 2pi ~ 4
       if (faceTwistDirs[twistedLayer] > 0)
         num = (4 - num) % 4;
       originalAngle = 0;
       natural = true; // the cube in the natural state
       twistLayers(cube, twistedLayer, num, twistedMode); // rotate the facelets
     }
     repaint();
   }
 }
 private final double[] eyeD = new double[3];
 public void mouseDragged(MouseEvent e) {
   if (pushed)
     return;
   if (dragging) {
     stopAnimation();
     int len = realMoveLength(move[curMove]);
     int pos = ((e.getX() - 1) * len * 2 / (width - 2) + 1) / 2;
     pos = Math.max(0, Math.min(len, pos));
     if (pos > 0)
       pos = arrayMovePos(move[curMove], pos);
     if (pos > movePos)
       doMove(cube, move[curMove], movePos, pos - movePos, false);
     if (pos < movePos)
       doMove(cube, move[curMove], pos, movePos - pos, true);
     movePos = pos;
     repaint();
     return;
   }
   int x = mirrored ? width - e.getX() : e.getX();
   int y = e.getY();
   int dx = x - lastX;
   int dy = y - lastY;
   if (editable && toTwist && !twisting && !animating) { // we do not twist but we can
     lastDragX = x;
     lastDragY = y;
     for (int i = 0; i < dragAreas; i++) { // check if inside a drag area
       double d1 = dragCornersX[i][0];
       double x1 = dragCornersX[i][1] - d1;
       double y1 = dragCornersX[i][3] - d1;
       double d2 = dragCornersY[i][0];
       double x2 = dragCornersY[i][1] - d2;
       double y2 = dragCornersY[i][3] - d2;
       double a = (y2 * (lastX - d1) - y1 * (lastY - d2)) / (x1 * y2 - y1 * x2);
       double b = (-x2 * (lastX - d1) + x1 * (lastY - d2)) / (x1 * y2 - y1 * x2);
       if (a > 0 && a < 1 && b > 0 && b < 1) { // we are in
         if (dx * dx + dy * dy < 144) // delay the decision about twisting
           return;
         dragX = dragDirsX[i];
         dragY = dragDirsY[i];
         double d = Math.abs(dragX * dx + dragY * dy) / Math.sqrt((dragX * dragX + dragY * dragY) * (dx * dx + dy * dy));
         if (d > 0.75) {
           twisting = true;
           twistedLayer = dragLayers[i];
           twistedMode = dragModes[i];
           break;
         }
       }
     }
     toTwist = false;
     lastX = lastDragX;
     lastY = lastDragY;
   }
   dx = x - lastX;
   dy = y - lastY;
   if (!twisting || animating) { // whole cube rotation
     vNorm(vAdd(eye, vScale(vCopy(eyeD, eyeX), dx * -0.016)));
     vNorm(vMul(eyeX, eyeY, eye));
     vNorm(vAdd(eye, vScale(vCopy(eyeD, eyeY), dy * 0.016)));
     vNorm(vMul(eyeY, eye, eyeX));
     lastX = x;
     lastY = y;
   }
   else {
     if (natural)
       splitCube(twistedLayer);
     currentAngle = 0.03 * (dragX * dx + dragY * dy) / Math.sqrt(dragX * dragX + dragY * dragY); // dv * cos a
   }
   repaint();
 }
 // status bar help strings
 private static final String[] buttonDescriptions = {
   "Clear to the initial state",
   "Show the previous step",
   "Play backward",
   "Stop",
   "Play",
   "Show the next step",
   "Go to the end",
   "Next sequence"
 };
 private String buttonDescription = "";
 public void mouseMoved(MouseEvent e) {
   int x = e.getX();
   int y = e.getY();
   String description = "Drag the cube with a mouse";
   if (x >= 0 && x < width) {
     if (y >= height && y < height + buttonHeight || y >= 0 && y < buttonHeight) {
       buttonPressed = selectButton(x, y);
       if (buttonPressed >= 0)
         description = buttonDescriptions[buttonPressed];
       if (buttonPressed == 3 && !animating)
         description = "Mirror the cube view";
     }
     else if (progressHeight > 0 && move.length > 0 && move[curMove].length > 0 && y >= height - progressHeight && y < height) {
       description = "Current progress";
     }
   }
   if (description != buttonDescription) {
     buttonDescription = description;
     showStatus(description);
   }
 }
 public void mouseClicked(MouseEvent e) {}
 public void mouseEntered(MouseEvent e) {}
 public void mouseExited(MouseEvent e) {}
 // Various useful vector functions
 private static double[] vCopy(double[] vector, double[] srcVec) {
   vector[0] = srcVec[0];
   vector[1] = srcVec[1];
   vector[2] = srcVec[2];
   return vector;
 }
 private static double[] vNorm(double[] vector) {
   double length = Math.sqrt(vProd(vector, vector));
   vector[0] /= length;
   vector[1] /= length;
   vector[2] /= length;
   return vector;
 }
 private static double[] vScale(double[] vector, double value) {
   vector[0] *= value;
   vector[1] *= value;
   vector[2] *= value;
   return vector;
 }
 private static double vProd(double[] vec1, double[] vec2) {
   return vec1[0] * vec2[0] + vec1[1] * vec2[1] + vec1[2] * vec2[2];
 }
 private static double[] vAdd(double[] vector, double[] srcVec) {
   vector[0] += srcVec[0];
   vector[1] += srcVec[1];
   vector[2] += srcVec[2];
   return vector;
 }
 private static double[] vSub(double[] vector, double[] srcVec) {
   vector[0] -= srcVec[0];
   vector[1] -= srcVec[1];
   vector[2] -= srcVec[2];
   return vector;
 }
 private static double[] vMul(double[] vector, double[] vec1, double[] vec2) {
   vector[0] = vec1[1] * vec2[2] - vec1[2] * vec2[1];
   vector[1] = vec1[2] * vec2[0] - vec1[0] * vec2[2];
   vector[2] = vec1[0] * vec2[1] - vec1[1] * vec2[0];
   return vector;
 }
 private static double[] vRotX(double[] vector, double angle) {
   double sinA = Math.sin(angle);
   double cosA = Math.cos(angle);
   double y = vector[1] * cosA - vector[2] * sinA;
   double z = vector[1] * sinA + vector[2] * cosA;
   vector[1] = y;
   vector[2] = z;
   return vector;
 }
 private static double[] vRotY(double[] vector, double angle) {
   double sinA = Math.sin(angle);
   double cosA = Math.cos(angle);
   double x = vector[0] * cosA - vector[2] * sinA;
   double z = vector[0] * sinA + vector[2] * cosA;
   vector[0] = x;
   vector[2] = z;
   return vector;
 }
 private static double[] vRotZ(double[] vector, double angle) {
   double sinA = Math.sin(angle);
   double cosA = Math.cos(angle);
   double x = vector[0] * cosA - vector[1] * sinA;
   double y = vector[0] * sinA + vector[1] * cosA;
   vector[0] = x;
   vector[1] = y;
   return vector;
 }

}

Personal tools