Você está na página 1de 35

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, M
ouseMotionListener {
// 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".charA
t(j)) {
hex[i] = j;
break;
}
}
}
bgColor = new Color(hex[0] * 16 + hex[1], hex[2] * 16 + hex[3], hex[4] * 1
6 + 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".charA
t(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)) == "0123456789abc
def".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)) == "0123456789wyo
rgbldmcpnk".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, fa
lse);
if (initialReversedMove.length > 0)
doMove(cube, initialReversedMove[0], 0, initialReversedMove[0].length, t
rue);
}
// 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 r
eversed) {
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 ani
mated) {
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 q
uarter 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 * (lTi
me - 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 != he
ight) {
width = size.width;
height = size.height;
image = createImage(width, height);
graphics = image.getGraphics();
textHeight = graphics.getFontMetrics().getHeight() - graphics.getFontMetri
cs().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 are
as
else { // in twisted state
// compute top observer
double cosA = Math.cos(originalAngle + currentAngle);
double sinA = Math.sin(originalAngle + currentAngle) * rotSign[twistedLa
yer];
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] * co
sA + 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[twistedLa
yer];
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] * c
osB + 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(perspNorma
l, 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 firs
t
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, progressHeigh
t - 1);
graphics.setColor(textColor);
int progress = (width - 2) * realMovePos(move[curMove], movePos) / rea
lMoveLength(move[curMove]);
graphics.fillRect(1, height - progressHeight + 1, progress, progressHe
ight - 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.getFontMetr
ics().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, buttonHeigh
t, 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[][][] bl
ocks, 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]); // pers
pective 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]); // perspe
ctive 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 slice
s
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, buttonP
ressed != 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, textH
eight);
}
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 facelet
s
}
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 * drag
X + 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;
}
}

Você também pode gostar