Rubik's Cube Source

(Last Update 14:32 on Wednesday, 12 January 2000)

// Rubik's Cube 3D simulator
// Neil Rashbrook August 18 1997
// Last modified May 19 1998
import java.awt.*;
public final class rubik extends java.applet.Applet implements Runnable {
private Thread rotator = null;
private int planes, av;
private Rectangle bounds;
private int lastX, lastY;
private double speed, cube;
private static final vector faceVec[] = {new vector(1, 0, 0), new vector(0, 1, 0), new vector(0, 0, 1), new vector(0, 0, -1), new vector(0, -1, 0), new vector(-1, 0, 0)}; // Normal vectors
private static final vector corners[] = {new vector(1, 1, 1), new vector(-1, 1, 1), new vector(1, -1, 1), new vector(1, 1, -1), new vector(-1, -1, 1), new vector(-1, 1, -1), new vector(1, -1, -1), new vector(-1, -1, -1)}; // Vertex co-ordinates
private static final corn faceCorn[] = {new corn(0, 3, 2, 1), new corn(0, 1, 3, 2), new corn(0, 2, 1, 3), new corn(7, 6, 5, 4), new corn(7, 4, 6, 5), new corn(7, 5, 4, 6)}; // Map faces to corners
private static final int sideFace[][] = {{1, 3, 4, 2}, {2, 5, 3, 0}, {0, 4, 5, 1}, {4, 0, 1, 5}, {5, 2, 0, 3}, {3, 1, 2, 4}}; // Which face is Up, Right, Down, Left
private static final int faceSide[][] = {{5, 0, 3, 1, 2, 4}, {3, 5, 0, 2, 4, 1}, {0, 3, 5, 4, 1, 2}, {1, 2, 4, 5, 0, 3}, {2, 4, 1, 3, 5, 0}, {4, 1, 2, 0, 3, 5}}; // Which direction a face is in
private rect sideBlock[][];
private static final rect sideBlock1[][] = {{null, null, null, null, null, new rect(0, 0, 1, 1)}};
private static final rect sideBlock2[][] = { // Partial faces to draw twisted cube
{new rect(0, 0, 2, 1), new rect(1, 0, 2, 2), new rect(0, 1, 2, 2), new rect(0, 0, 1, 2), null, new rect(0, 0, 2, 2)},
{new rect(0, 1, 2, 2), new rect(0, 0, 1, 2), new rect(0, 0, 2, 1), new rect(1, 0, 2, 2), new rect(0, 0, 2, 2), null}};
private static final rect sideBlock3[][] = { // Partial faces to draw twisted cube
{new rect(0, 0, 3, 1), new rect(2, 0, 3, 3), new rect(0, 2, 3, 3), new rect(0, 0, 1, 3), null, new rect(0, 0, 3, 3)},
{new rect(0, 1, 3, 2), new rect(1, 0, 2, 3), new rect(0, 1, 3, 2), new rect(1, 0, 2, 3), null, null},
{new rect(0, 2, 3, 3), new rect(0, 0, 1, 3), new rect(0, 0, 3, 1), new rect(2, 0, 3, 3), new rect(0, 0, 3, 3), null}};
private static final rect sideBlock4[][] = { // Partial faces to draw twisted cube
{new rect(0, 0, 4, 1), new rect(3, 0, 4, 4), new rect(0, 3, 4, 4), new rect(0, 0, 1, 4), null, new rect(0, 0, 4, 4)},
{new rect(0, 1, 4, 2), new rect(2, 0, 3, 4), new rect(0, 2, 4, 3), new rect(1, 0, 2, 4), null, null},
{new rect(0, 2, 4, 3), new rect(1, 0, 2, 4), new rect(0, 1, 4, 2), new rect(2, 0, 3, 4), null, null},
{new rect(0, 3, 4, 4), new rect(0, 0, 1, 4), new rect(0, 0, 4, 1), new rect(3, 0, 4, 4), new rect(0, 0, 4, 4), null}};
private static final rect sideBlocks[][][] = {null, sideBlock1, sideBlock2, sideBlock3, sideBlock4};
private static final int circleOrder3[] = {0, 0, 0, 1, 2, 2, 2, 1, 0, 0}; // Used to rotate colours on sides
private static final int circleOrder4[] = {0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 2, 1, 0, 0, 0}; // Used to rotate colours on sides
private int cornerOrder[];
private int faceCols[][][]; // Colours on faces [face][row][col]
private Color colList[][]; // Colour gradients for light calculation
private Color bgColor, textColor, disabledColor, enabledColor, activeColor;
private int colMap[];
private boolean colUsed[];
private drag dragInfo; // List of drag regions
private facelet faceInfo; // List of facelets
private int twistFace = 0, twistMode = 2; // Currently twisting block
private vector eyeX, eyeY, eyeZ;
private double phi, phibase = 0, currDragX, currDragY;
private boolean naturalState = true, twisting = false, OKtoTwist = false, calcDrag = true, doubleClick = false;
private boolean sticky = false, grabrect = false, graball = false, plain = false, copyright = false;
private Graphics offGraphics, tiledGraphics; // For double-buffering
private Image offImage, tiledImage, bkImage = null;
private String select, facelets;
private int backX = 0, backY = 0, curCol = 0, faceCol = 6, iconSize = 0;
private static final int ctlX[][] = {{1, 3, 3, 1}, {-1, -3, -3, -1}, {1, 2, 2, 3, 3, 2, 2, 1}, {-1, -2, -2, -3, -3, -2, -2, -1}};
private static final int ctlY[][] = {{2, 1, 3, 2}, {2, 1, 3, 2}, {-2, -1, -2, -1, -3, -2, -3, -2}, {-2, -1, -2, -1, -3, -2, -3, -2}};
private Polygon ctlP[];
private boolean ctlM[];
private queue moves, undo, redo;
private Frame cursorFrame;
private static final float two = 2;
private static final String colors[] = {"white", "red", "yellow", "green", "orange", "blue", "cyan", "magenta", "pink"};
private static final float hue[] = {0, 0, two / 12, two / 6, two / 24, two / 3, two / 4, two * 5 / 12, 0};
public boolean addMove(int face, int quads, int mode) {
if (face >= 0 && face < 6 && quads > 0 && quads < 4 && mode >= 0 && mode < planes) {
moves.add(new queue(face, quads, mode, planes));
return true;
}
return false;
}
private void addMoves(String param, queue redo) {
char chars[] = param.toCharArray();
for (int i = 0; i < chars.length; ) {
int face = 0, quads = 0, mode = 0;
switch (chars[i++]) {
default: continue;
case 'F': case 'f': face = 0; break;
case 'U': case 'u': case 'T': case 't': face = 1; break;
case 'L': case 'l': face = 2; break;
case 'R': case 'r': face = 3; break;
case 'D': case 'd': face = 4; break;
case 'B': case 'b': face = 5; break;
}
if (i < chars.length) switch (chars[i]) {
case '1': case 'o': case '+': case '>': case ']': case '}': case '¹': quads = 3; i++; break;
case '2': case 'i': case '.': case '=': case '\\': case '|': case '²': quads = 2; i++; break;
case '3': case 'a': case '-': case '<': case '[': case '{': case '³': quads = 1; i++; break;
}
if (planes > 2 && i < chars.length) switch (chars[i]) {
case '^': case '~': case '_': case '?': case '%': mode = 1; i++;
}
if (redo == null) colorTwist(faceCols, face, quads == 0 ? 3 : quads, mode);
else if (quads == 0) redo.add(face, 3, mode, planes);
else redo.add(new queue(face, quads, mode, planes));
mode = 0;
}
}
private void colorTwist(int faceCols[][][], int faceNum, int quads, int mode) { // Shift colored squares
int i, j, k, l;
int buffer[] = new int[planes * 4];
if (mode == 0) { // Mode is 1 (middle) or 0 (face)
switch (planes) {
case 4:
for (i = 0; i < 12; i++) // Read existing colours
buffer[i] = faceCols[faceNum][circleOrder4[i]][circleOrder4[i + 3]];
j = quads * 3; // Write colours offset
for (i = 0; i < 12; i++) {
faceCols[faceNum][circleOrder4[i]][circleOrder4[i + 3]] = buffer[j];
j = (j + 1) % 12;
}
buffer[0] = faceCols[faceNum][1][1];
buffer[1] = faceCols[faceNum][1][2];
buffer[2] = faceCols[faceNum][2][2];
buffer[3] = faceCols[faceNum][2][1];
faceCols[faceNum][1][1] = buffer[quads];
faceCols[faceNum][1][2] = buffer[(quads + 1) & 3];
faceCols[faceNum][2][2] = buffer[(quads + 2) & 3];
faceCols[faceNum][2][1] = buffer[(quads + 3) & 3];
break;
case 3:
for (i = 0; i < 8; i++) // Read existing colours
buffer[i] = faceCols[faceNum][circleOrder3[i]][circleOrder3[i + 2]];
j = quads * 2; // Write colours offset
for (i = 0; i < 8; i++) {
faceCols[faceNum][circleOrder3[i]][circleOrder3[i + 2]] = buffer[j];
j = (j + 1) & 7;
}
break;
case 2:
buffer[0] = faceCols[faceNum][0][0];
buffer[1] = faceCols[faceNum][0][1];
buffer[2] = faceCols[faceNum][1][1];
buffer[3] = faceCols[faceNum][1][0];
faceCols[faceNum][0][0] = buffer[quads];
faceCols[faceNum][0][1] = buffer[(quads + 1) & 3];
faceCols[faceNum][1][1] = buffer[(quads + 2) & 3];
faceCols[faceNum][1][0] = buffer[(quads + 3) & 3];
break;
}
}
j = 0;
for (i = 0; i < 4; i++) { // Read existing colours
l = sideFace[faceNum][i];
switch(faceSide[l][faceNum]) {
case 0:
for (k = 0; k < planes; k++) buffer[j++] = faceCols[l][mode][k];
break;
case 1:
for (k = 0; k < planes; k++) buffer[j++] = faceCols[l][k][planes - 1 - mode];
break;
case 2:
for (k = 0; k < planes; k++) buffer[j++] = faceCols[l][planes - 1 - mode][planes - 1 - k];
break;
case 3:
for (k = 0; k < planes; k++) buffer[j++] = faceCols[l][planes - 1 - k][mode];
break;
}
}
j = quads * planes; // Write colours offset
for (i = 0; i < 4; i++) {
l = sideFace[faceNum][i];
switch(faceSide[l][faceNum]) {
case 0:
for (k = 0; k < planes; k++) faceCols[l][mode][k] = buffer[j++];
break;
case 1:
for (k = 0; k < planes; k++) faceCols[l][k][planes - 1 - mode] = buffer[j++];
break;
case 2:
for (k = 0; k < planes; k++) faceCols[l][planes - 1 - mode][planes - 1 - k] = buffer[j++];
break;
case 3:
for (k = 0; k < planes; k++) faceCols[l][planes - 1 - k][mode] = buffer[j++];
break;
}
j %= planes * 4;
}
}
public void destroy() {
rotator.stop();
}
private void drawPolygon(int i) {
offGraphics.setColor(ctlM[i] ? activeColor : disabledColor);
offGraphics.fillPolygon(ctlP[i]);
offGraphics.setColor(textColor);
offGraphics.drawPolygon(ctlP[i]);
}
private void findCol(int face) {
int color = colMap[face];
if (color >= 0) colUsed[color] = false;
do if (++color == 9) color = 0;
while (colUsed[color]);
colUsed[color] = true;
colMap[face] = color;
}
private Color findColor(String name, Color value) { // Convert hexadecimal RGB parameter to color
try {
return new Color(Integer.parseInt(getParameter(name), 16));
} catch (Exception e) {
return value;
}
}
private vector findEye(String param) {
try {
return new vector(getDouble(param + "x"), getDouble(param + "y"), getDouble(param + "z"));
} catch (Exception e) {
return null;
}
}
private void fixBlock(vector beyeZ, vector beyeX, vector beyeY, int mode) { // Draw cube or sub-cube
double xCoord[] = new double[8], yCoord[] = new double[8]; // Projected co-ordinates (on screen)
vector light = new vector(beyeX);
light.sub(beyeY);
light.addmult(beyeZ, -3);
for (int i = 0; i < 8; i++) { // Project 3D co-ordinates into 2D screen ones
xCoord[i] = (bounds.width * 0.5 + cube * beyeX.mult(corners[i]));
yCoord[i] = (bounds.height * 0.5 - cube * beyeY.mult(corners[i]));
}
for (int i = 0; i < 6; i++) if (beyeZ.mult(faceVec[i]) > 0.001) { // Face towards us? Draw it.
double sx = xCoord[faceCorn[i].nw];
double sy = yCoord[faceCorn[i].nw];
double sdxh = (xCoord[faceCorn[i].ne] - sx) / planes;
double sdxv = (xCoord[faceCorn[i].sw] - sx) / planes;
double sdyh = (yCoord[faceCorn[i].ne] - sy) / planes;
double sdyv = (yCoord[faceCorn[i].sw] - sy) / planes;
int j = faceSide[i][twistFace];
rect block = mode < 0 ? sideBlock[0][5] : sideBlock[mode][j];
if (block == null) { // Just draw blank
int k = i == twistFace ? mode : planes - 1 - mode; // Move face away
sx = (sx * (planes - k) + xCoord[faceCorn[i].bk] * k) / planes;
sy = (sy * (planes - k) + yCoord[faceCorn[i].bk] * k) / planes;
offGraphics.setColor(Color.black);
offGraphics.fillPolygon(new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][5]));
offGraphics.drawPolygon(new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][5]));
} else { // First draw blank
paral p = new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, block);
offGraphics.setColor(Color.black);
offGraphics.fillPolygon(p);
offGraphics.drawPolygon(p);
int thisFaceCols[][] = faceCols[i];
Color thisList[] = colList[plain ? 19 : (int)(13 * (0.5 - light.cosAng(faceVec[i])))];
//Color thisList[] = colList[(int)(9.6 * (1 - light.cosAng(faceVec[i])))];
// Try also (int)(13 * (0.5 - light.cosAng(faceVec[i])))
for (int k = block.top; k < block.bottom; k++) { // Then draw colored squares
int thisRowCols[] = thisFaceCols[k];
for (int l = block.left; l < block.right; l++) {
paral pp = new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, l, k);
Color c = thisList[colMap[thisRowCols[l]]];
offGraphics.setColor(c);
offGraphics.fillPolygon(pp);
offGraphics.setColor(thisList[10]);
offGraphics.drawPolygon(pp);
if (calcDrag && !copyright) faceInfo = new facelet(faceInfo, i, k, l, pp);
}
}
if (calcDrag) { // Determine allowed drag regions and directions
if (mode < 0) {
if (!grabrect) switch (planes) {
case 4: // Eight drag regions
dragInfo = new drag(dragInfo, -sdxv, -sdyv, 1, sideFace[i][3], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][3], planes, graball, false));
dragInfo = new drag(dragInfo, -sdxh, -sdyh, 1, sideFace[i][2], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][2], planes, graball, true));
case 3: // Six drag regions
dragInfo = new drag(dragInfo, sdxv, sdyv, 1, sideFace[i][1], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][1], planes, graball, false));
dragInfo = new drag(dragInfo, sdxh, sdyh, 1, sideFace[i][0], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][0], planes, graball, true));
case 2: // Four drag regions
dragInfo = new drag(dragInfo, -sdxv, -sdyv, 0, sideFace[i][3], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][3], planes, graball, false));
dragInfo = new drag(dragInfo, -sdxh, -sdyh, 0, sideFace[i][2], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][2], planes, graball, true));
dragInfo = new drag(dragInfo, sdxv, sdyv, 0, sideFace[i][1], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][1], planes, graball, false));
dragInfo = new drag(dragInfo, sdxh, sdyh, 0, sideFace[i][0], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][0], planes, graball, true));
} else if (!graball) switch (planes) {
case 4: // Eight drag regions
dragInfo = new drag(dragInfo, -sdxv, -sdyv, 1, sideFace[i][3], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][3], false));
dragInfo = new drag(dragInfo, -sdxh, -sdyh, 1, sideFace[i][2], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][2], true));
case 3: // Six drag regions
dragInfo = new drag(dragInfo, sdxv, sdyv, 1, sideFace[i][1], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][1], false));
dragInfo = new drag(dragInfo, sdxh, sdyh, 1, sideFace[i][0], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][0], true));
case 2: // Four drag regions
dragInfo = new drag(dragInfo, -sdxv, -sdyv, 0, sideFace[i][3], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][3], false));
dragInfo = new drag(dragInfo, -sdxh, -sdyh, 0, sideFace[i][2], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][2], true));
dragInfo = new drag(dragInfo, sdxv, sdyv, 0, sideFace[i][1], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][1], false));
dragInfo = new drag(dragInfo, sdxh, sdyh, 0, sideFace[i][0], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][0], true));
} else switch (planes) {
case 4: // Eight drag regions
dragInfo = new drag(dragInfo, -sdxv, -sdyv, 1, sideFace[i][3], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][3]));
dragInfo = new drag(dragInfo, -sdxh, -sdyh, 1, sideFace[i][2], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][2]));
case 3: // Six drag regions
dragInfo = new drag(dragInfo, sdxv, sdyv, 1, sideFace[i][1], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][1]));
dragInfo = new drag(dragInfo, sdxh, sdyh, 1, sideFace[i][0], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[1][0]));
case 2: // Four drag regions
dragInfo = new drag(dragInfo, -sdxv, -sdyv, 0, sideFace[i][3], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][3]));
dragInfo = new drag(dragInfo, -sdxh, -sdyh, 0, sideFace[i][2], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][2]));
dragInfo = new drag(dragInfo, sdxv, sdyv, 0, sideFace[i][1], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][1]));
dragInfo = new drag(dragInfo, sdxh, sdyh, 0, sideFace[i][0], new paral(sx, sy, sdxh, sdyh, sdxv, sdyv, sideBlock[0][0]));
}
} else if (mode == twistMode && i != twistFace) { // The small sub-cube (twistable part)
switch (j) { // Determine drag direction
case 0:
dragInfo = new drag(dragInfo, sdxh, sdyh, twistMode, twistFace, p);
break;
case 1:
dragInfo = new drag(dragInfo, sdxv, sdyv, twistMode, twistFace, p);
break;
case 2:
dragInfo = new drag(dragInfo, -sdxh, -sdyh, twistMode, twistFace, p);
break;
case 3:
dragInfo = new drag(dragInfo, -sdxv, -sdyv, twistMode, twistFace, p);
break;
}
}
}
}
}
}
public String getAppletInfo() { // for appletviewer 1.1
return "Rubik's Cube™ Applet © 1997-8 Neil Rashbrook";
}
private double getDouble(String param) throws Exception {
return Double.valueOf(getParameter(param)).doubleValue();
}
public int getFacelet(int face, int row, int col) {
if (face >= 0 && face < 6 && row >= 0 && row < planes && col >= 0 && col < planes) return faceCols[face][row][col];
else return -1;
}
private char[] getFacelets() {
char facechars[] = new char[planes * planes * 6];
int l = 0;
for (int i = 0; i < 6; i++) for (int j = 0; j < planes; j++) for (int k = 0; k < planes; k++) facechars[l++] = (char)(faceCols[i][j][k] + '0');
return facechars;
}
public String[][] getParameterInfo() { // for appletviewer 1.1
return new String[][] {
{"alink", "000000-FFFFFF", "Arrow highlight color"},
{"background", "url", "Background image"},
{"backx", "int", "Background width adjustment"},
{"backy", "int", "Background height adjustment"},
{"bgcolor", "000000-FFFFFF", "Background color"},
{"eyeXx", "double", "EyeX vector x component"},
{"eyeXy", "double", "EyeX vector y component"},
{"eyeXz", "double", "EyeX vector z component"},
{"eyeYx", "double", "EyeY vector x component"},
{"eyeYy", "double", "EyeY vector y component"},
{"eyeYz", "double", "EyeY vector z component"},
{"eyeZx", "double", "EyeZ vector x component"},
{"eyeZy", "double", "EyeZ vector y component"},
{"eyeZz", "double", "EyeZ vector z component"},
{"face0", "String", "Face 0 [default white]"},
{"face1", "String", "Face 1 [default red]"},
{"face2", "String", "Face 2 [default yellow]"},
{"face3", "String", "Face 3 [default green]"},
{"face4", "String", "Face 4 [default orange]"},
{"face5", "String", "Face 5 [default blue]"},
{"facelets1", "String", "1x1x1 facelets"},
{"facelets2", "String", "2x2x2 facelets"},
{"facelets3", "String", "3x3x3 facelets"},
{"facelets4", "String", "4x4x4 facelets"},
{"focus", "boolean", "Request focus"},
{"graball", "boolean", "Grab on all of face"},
{"grabrect", "boolean", "Grab rectangles"},
{"link", "000000-FFFFFF", "Arrow body color"},
{"moves2", "String", "2x2x2 initial moves"},
{"moves3", "String", "3x3x3 initial moves"},
{"moves4", "String", "4x4x4 initial moves"},
{"planes", "int", "Initial cube size (default 3)"},
{"rotation", "int", "1-3 radians per second, 0 jumps [default 2]"},
{"select", "String", "Selectable planes [default 1, 2, 3, 4]"},
{"steps2", "String", "2x2x2 script steps"},
{"steps3", "String", "3x3x3 script steps"},
{"steps4", "String", "4x4x4 script steps"},
{"sticky", "boolean", "Allow sticky dragging"},
{"text", "000000-FFFFFF", "Arrow border color"},
{"vlink", "000000-FFFFFF", "Arrow unused color"}};
}
private int getPlanes() {
String s = getParameter("planes");
if ("1".equals(s)) return 1;
if ("2".equals(s)) return 2;
if ("4".equals(s)) return 4;
return 3;
}
private int getRotation() {
String s = getParameter("rotation");
if ("0".equals(s)) return 0;
if ("1".equals(s)) return 1;
if ("3".equals(s)) return 3;
return 2;
}
private void home() {
try {
getAppletContext().showDocument(new java.net.URL("http://www.neil.parkwaycc.co.uk/rubik/"), "_blank");
} finally {
return;
}
}
public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) {
if ((flags & ALLBITS) != 0) {
tiledImage = null;
repaint(1);
}
return (flags & (ALLBITS | ABORT)) == 0;
}
public void init() {
Container comp = this;
while (comp != null && !(comp instanceof Frame)) comp = comp.getParent();
cursorFrame = (Frame)comp;
moves = new queue();
undo = new queue();
//redo = new queue();
Polygon p = new Polygon();
ctlP = new Polygon[] {p, p, p, p};
ctlM = new boolean[4];
cornerOrder = new int[6];
faceCols = new int[6][4][4];
colList = new Color[20][11];
for (int i = 0; i < 20; i++) { // Generate colour gradients
float s = (85 - i) / 85f, b = (i + i + 19) / 57f;
colList[i][0] = Color.getHSBColor(0, 0, b);
for (int j = 1; j < 8; j++) colList[i][j] = Color.getHSBColor(hue[j], s, b);
colList[i][8] = Color.getHSBColor(0, (85 - i) / 115f, b);
colList[i][9] = Color.getHSBColor(0, 0, b / 2);
colList[i][10] = Color.black;
}
// Load parmeters
av = getRotation();
select = getParameter("select");
if (select == null) select = "1234";
copyright = "Neil Rashbrook".equalsIgnoreCase(getParameter("copyright"));
sticky = "true".equalsIgnoreCase(getParameter("sticky"));
grabrect = "true".equalsIgnoreCase(getParameter("grabrect"));
graball = "true".equalsIgnoreCase(getParameter("graball"));
bgColor = findColor("bgcolor", Color.lightGray);
textColor = findColor("text", Color.black);
disabledColor = findColor("link", Color.blue);
enabledColor = findColor("vlink", Color.magenta);
activeColor = findColor("alink", Color.red);
eyeX = findEye("eyeX");
eyeY = findEye("eyeY");
eyeZ = findEye("eyeZ");
if (eyeY == null && (eyeX == null || eyeZ == null) || eyeX == null && eyeZ == null) {
eyeZ = new vector(0.8560, 0.1344, 0.4992); // Initial observer co-ordinate axes (view)
eyeX = new vector(0.4992, 0.0656, -0.8640); // (sideways)
eyeY = new vector(eyeZ, eyeX);
}
if (eyeY == null) eyeY = new vector(eyeZ, eyeX);
else if (eyeX == null) eyeX = new vector(eyeY, eyeZ);
else if (eyeZ == null) eyeZ = new vector(eyeX, eyeY);
colMap = new int[8];
colUsed = new boolean[9];
for (int i = 0; i < 6; i++) {
String s = getParameter("face" + i);
int j = 8;
while (j >= 0 && (colUsed[j] || !colors[j].equalsIgnoreCase(s))) j--;
if (j >= 0) colUsed[colMap[i] = j] = true;
else colMap[i] = -1;
}
for (int i = 0; i < 6; i++) if (colMap[i] < 0) findCol(i);
colMap[6] = 9;
colMap[7] = 10;
rotator = new Thread(this, "Rotator");
rotator.setDaemon(true);
rotator.start();
setPlanes(getPlanes());
if ("true".equals(getParameter("focus"))) try {
requestFocus(); // for appletviewer 1.1
} catch (Exception e) {
}
}
public boolean isFocusTraversable() { // for appletviewer 1.1
return true;
}
public boolean keyDown(Event evt, int key) {
if ((evt.modifiers & evt.ALT_MASK) != 0) return false;
if (key >= 1002 && key <= 1007) {
switch (key) {
case 1002: // PgUp
eyeY.addmultnorm(eyeX, 2 / speed);
eyeX.multnorm(eyeY, eyeZ);
break;
case 1003: // PgDn
eyeY.addmultnorm(eyeX, -2 / speed);
eyeX.multnorm(eyeY, eyeZ);
break;
case 1004: // Up
eyeZ.addmultnorm(eyeY, -2 / speed);
eyeY.multnorm(eyeZ, eyeX);
break;
case 1005: // Down
eyeZ.addmultnorm(eyeY, 2 / speed);
eyeY.multnorm(eyeZ, eyeX);
break;
case 1006: // Left
eyeX.addmultnorm(eyeZ, -2 / speed);
eyeZ.multnorm(eyeX, eyeY);
break;
case 1007: // Right
eyeX.addmultnorm(eyeZ, 2 / speed);
eyeZ.multnorm(eyeX, eyeY);
break;
}
calcDrag = true;
repaint(1);
return true;
}
if (key >= 64 && key < 128) key &= 31;
switch (key) {
case ',': moves.append(undo, true); break;
case '.': moves.append(redo, true); break;
case '<': moves.append(undo, false); break;
case '>': moves.append(redo, false); break;
case 49: case 50: case 51: case 52: // 1, 2, 3, 4
if (select.indexOf(key) >= 0 && moves.iswaiting()) setPlanes(key - 48);
return true;
case 1: // a
if (evt.controlDown()) av = evt.shiftDown() ? 0 : 1;
else av = evt.shiftDown() ? 3 : 2;
return true;
case 2: // b
rotate(eyeZ, eyeX, eyeY, -1, evt);
return true;
case 3: // c
if (evt.controlDown()) curCol = (curCol + 1) % 6;
else if (evt.shiftDown()) {
findCol(curCol);
repaint(1);
}
else if (moves.iswaiting()) setPlanes(planes); // Clear
return true;
case 4: // d
rotate(eyeY, eyeZ, eyeX, -1, evt);
return true;
case 6: // f
rotate(eyeZ, eyeX, eyeY, 1, evt);
return true;
case 7: // g
grabrect = evt.shiftDown();
graball = evt.controlDown();
calcDrag = true;
repaint(1);
return true;
case 8: // h
home();
return true;
case 12: // l
rotate(eyeX, eyeY, eyeZ, -1, evt);
return true;
case 16: // p
plain = evt.shiftDown();
repaint(1);
return true;
case 18: // r
rotate(eyeX, eyeY, eyeZ, 1, evt);
return true;
case 19: // s
if (evt.controlDown()) sticky = evt.shiftDown();
else if (facelets == null && planes > 1) {
calcDrag = false;
dragInfo = null;
if (cursorFrame != null) cursorFrame.setCursor(Frame.DEFAULT_CURSOR);
int old = -1;
for (int i = 0; i < 20; i++) {
int face;
do face = (int)(Math.random() * 3);
while (face == old);
moves.add(face, (int)(Math.random() * 3) + 1, (int)(Math.random() * planes), planes);
old = face;
}
return true;
}
break;
case 20: case 21: // t, u
rotate(eyeY, eyeZ, eyeX, 1, evt);
return true;
case 23: // w
writeParameters();
return true;
}
return false;
}
public boolean mouseDown(Event evt, int x, int y) {
if (evt.metaDown()) {
doubleClick = false;
if (moves.iswaiting()) {
for (facelet info = faceInfo; info != null; info = info.next) {
if (info.poly.inside(x, y)) {
if (evt.controlDown()) {
if (evt.shiftDown()) faceCols[info.face][info.row][info.col] = (faceCols[info.face][info.row][info.col] + 1) & 7;
else faceCols[info.face][info.row][info.col] = faceCols[info.face][info.row][info.col] >= 6 ? info.face : 6;
} else {
if (evt.shiftDown()) faceCols[info.face][info.row][info.col] = faceCol;
else {
faceCol = faceCols[info.face][info.row][info.col];
break;
}
}
facelets = "";
repaint(1);
break;
}
}
}
} else if (evt.shiftDown()) {
if (moves.iswaiting()) {
for (drag info = dragInfo; info != null; info = info.next) { // Check if inside a drag region
if (info.poly.inside(x, y)) {
moves.add(new queue(info.face, evt.controlDown() ? 2 : 1, info.mode, planes));
break;
}
}
}
} else if (evt.controlDown()) {
if (moves.iswaiting()) {
for (drag info = dragInfo; info != null; info = info.next) { // Check if inside a drag region
if (info.poly.inside(x, y)) {
moves.add(new queue(info.face, 3, info.mode, planes));
break;
}
}
}
} else {
//for (int i = 0; i < 4; i++) if (ctlM[i] && moves.append((i & 1) != 0 ? redo : undo, (i & 2) != 0) && !rotator.isAlive()) rotator.start();
for (int i = 0; i < 4; i++) if (ctlM[i] && moves.append((i & 1) != 0 ? redo : undo, (i & 2) != 0)) doubleClick = false;
if (doubleClick && evt.clickCount > 1) home();
else {
doubleClick = true;
if (!twisting) {
lastX = x;
lastY = y;
OKtoTwist = moves.iswaiting();
}
if (!moves.iswaiting() && cursorFrame != null) cursorFrame.setCursor(Frame.DEFAULT_CURSOR);
}
}
return true;
}
public boolean mouseDrag(Event evt, int x, int y) {
doubleClick = false;
if (!evt.metaDown()) {
int dx = x - lastX, dy = y - lastY;
if (OKtoTwist) {
for (drag info = dragInfo; info != null; info = info.next) { // Check if inside a drag region
if (info.poly.inside(lastX, lastY)) {
currDragX = info.dirX;
currDragY = info.dirY;
double d = currDragX * dx + currDragY * dy;
d = d * d / ((currDragX * currDragX + currDragY * currDragY) * (dx * dx + dy * dy));
if (d > 0.7) { // Dragging the right way
twistMode = info.mode;
twistFace = info.face;
naturalState = false;
twisting = true;
d = Math.sqrt(currDragX * currDragX + currDragY * currDragY) * speed;
currDragX /= d;
currDragY /= d;
if (planes == 3 && twistMode == 1 && twistFace >= 3) {
twistFace = 5 - twistFace;
currDragX = -currDragX;
currDragY = -currDragY;
}
break;
}
}
}
}
OKtoTwist = false;
if (twisting) // Twist, compute twisting angle phi
phi = currDragX * (x - lastX) + currDragY * (y - lastY);
else { // Normal rotation
// Vertical shift
eyeZ.addmultnorm(eyeX, -dx / speed);
eyeX.multnorm(eyeY, eyeZ);
// Horizontal shift
eyeZ.addmultnorm(eyeY, dy / speed);
eyeY.multnorm(eyeZ, eyeX);
lastX = x;
lastY = y;
}
repaint(1);
}
return true;
}
public boolean mouseEnter(Event evt, int x, int y) {
return true;
}
public boolean mouseExit(Event evt, int x, int y) {
for (int i = 0; i < 4; i++) {
if (ctlM[i] && moves.iswaiting()) repaint(1);
ctlM[i] = false;
}
return false;
}
public boolean mouseMove(Event evt, int x, int y) {
int nCursor = Frame.DEFAULT_CURSOR;
for (int i = 0; i < 4; i++) {
boolean b = ctlP[i].inside(x, y);
if (ctlM[i] != b && moves.iswaiting()) repaint(1);
ctlM[i] = b;
if (b && naturalState && moves.isempty()) switch (i) {
case 0: if (!undo.isempty()) nCursor = Frame.HAND_CURSOR; break;
case 1: if (redo != null && !redo.isempty() && !redo.startsWith(undo)) nCursor = Frame.HAND_CURSOR; break;
case 2: if (!undo.isnearempty()) nCursor = Frame.HAND_CURSOR; break;
case 3: if (redo != null && !redo.isnearempty()) nCursor = Frame.HAND_CURSOR; break;
}
}
if (cursorFrame != null) {
for (drag info = dragInfo; info != null; info = info.next) { // Check if inside a drag region
if (info.poly.inside(x, y)) {
if (nCursor != Frame.DEFAULT_CURSOR) nCursor = Frame.MOVE_CURSOR;
else {
currDragX = info.dirX;
currDragY = info.dirY;
double d0 = currDragY * currDragY / ((currDragX * currDragX + currDragY * currDragY));
nCursor = Frame.N_RESIZE_CURSOR;
for (int dy = -1; dy <= 1; dy++) {
double d = currDragX + currDragY * dy;
d = d * d / ((currDragX * currDragX + currDragY * currDragY) * (1 + dy * dy));
if (d > d0) {
d0 = d;
if (dy < 0) nCursor = Frame.NE_RESIZE_CURSOR;
else if (dy > 0) nCursor = Frame.SE_RESIZE_CURSOR;
else nCursor = Frame.E_RESIZE_CURSOR;
}
}
}
}
}
cursorFrame.setCursor(nCursor);
}
return true;
}
public boolean mouseUp(Event evt, int x, int y) {
if (twisting) { // We have let go of the mouse when twisting
twisting = false;
phibase += phi; // Save twist angle
phi = 0;
if (!sticky) moves.add(new queue(twistFace, phibase < 0 ? -(int)(0.5 - phibase * 2 / Math.PI) : (int)(0.5 + phibase * 2 / Math.PI), twistMode, planes));
else {
double qu = phibase;
while (qu < 0) qu += 40 * Math.PI;
int quads = ((int)(qu * 10 / Math.PI));
if (quads % 5 == 0 || quads % 5 == 4) { // Close enough to a corner?
quads = ((quads + 1) / 5) & 3;
if (quads != 0) {
colorTwist(faceCols, twistFace, quads, twistMode); // And shift the colored squares
redo.sub(twistFace, 4 - quads, twistMode, planes);
undo.sub(twistFace, 4 - quads, twistMode, planes);
}
phibase = 0;
naturalState = true; // Return the cube to its natural state
}
}
}
if (moves.iswaiting()) {
calcDrag = true;
paint(getGraphics());
mouseMove(evt, x, y);
}
return true;
}
public synchronized void paint(Graphics g) {
Color bgColor = this.bgColor == null ? getBackground() : this.bgColor;
if (!bounds().equals(bounds)) { // for appletviewer 1.1
bounds = bounds();
speed = Math.min(bounds.width, bounds.height) / 3.0;
cube = speed * Math.sqrt(0.75);
offImage = createImage(bounds.width, bounds.height); // Double buffer
offGraphics = offImage.getGraphics();
iconSize = Math.min(bounds.width, bounds.height) / 20;
int tmpX[] = new int[8];
int tmpY[] = new int[8];
for (int i = 0; i < 4; i++) ctlP[i] = new Polygon(resize(tmpX, ctlX[i], iconSize, bounds.width), resize(tmpY, ctlY[i], iconSize, bounds.height), ctlX[i].length);
}
if (bkImage == null || (checkImage(bkImage, this) & ALLBITS) == 0) {
offGraphics.setColor(bgColor); // Clear drawing buffer
offGraphics.fillRect(0, 0, bounds.width, bounds.height);
} else { // Fill background
if (tiledImage == null) {
tiledImage = createImage(bounds.width, bounds.height);
tiledGraphics = tiledImage.getGraphics();
int imgWidth = bkImage.getWidth(this);
int imgHeight = bkImage.getHeight(this);
for (int i = backX; i < bounds.width; i += imgWidth) {
for (int j = backY; j < bounds.height; j += imgHeight) {
tiledGraphics.drawImage(bkImage, i, j, bgColor, this);
}
}
}
offGraphics.drawImage(tiledImage, 0, 0, this);
}
if (calcDrag) {
dragInfo = null;
faceInfo = null;
}
if (naturalState)
fixBlock(eyeZ, eyeX, eyeY, -1); // Draw cube
else {
vector TeyeZ = new vector(eyeZ); // In twisted state? Compute top observer
vector TeyeX = new vector(eyeX);
double Cphi = Math.cos(phi + phibase);
double Sphi = - Math.sin(phi + phibase);
switch(twistFace) { // Twist around which axis?
case 0: // -x
TeyeZ.rotateX(Cphi, -Sphi);
TeyeX.rotateX(Cphi, -Sphi);
break;
case 1: // y
TeyeZ.rotateY(Cphi, Sphi);
TeyeX.rotateY(Cphi, Sphi);
break;
case 2: // -z
TeyeZ.rotateZ(Cphi, -Sphi);
TeyeX.rotateZ(Cphi, -Sphi);
break;
case 3: // z
TeyeZ.rotateZ(Cphi, Sphi);
TeyeX.rotateZ(Cphi, Sphi);
break;
case 4: // -y
TeyeZ.rotateY(Cphi, -Sphi);
TeyeX.rotateY(Cphi, -Sphi);
break;
case 5: // x
TeyeZ.rotateX(Cphi, Sphi);
TeyeX.rotateX(Cphi, Sphi);
break;
}
vector TeyeY = new vector(TeyeZ, TeyeX);
if (eyeZ.mult(faceVec[twistFace]) < 0) { // Top facing away? Draw it first
for (int i = 0; i < planes; i++) {
if (twistMode == i) fixBlock(TeyeZ, TeyeX, TeyeY, i);
else fixBlock(eyeZ, eyeX, eyeY, i);
}
} else {
for (int i = planes; i-- > 0; ) {
if (twistMode == i) fixBlock(TeyeZ, TeyeX, TeyeY, i);
else fixBlock(eyeZ, eyeX, eyeY, i);
}
}
}
calcDrag = false;
if (naturalState && moves.isempty()) {
if (!undo.isempty()) drawPolygon(0);
if (redo != null && !redo.isempty() && !redo.startsWith(undo)) drawPolygon(1);
if (!undo.isnearempty()) drawPolygon(2);
if (redo != null && !redo.isnearempty()) drawPolygon(3);
}
g.drawImage(offImage, 0, 0, this);
notifyAll();
}
private void perform(queue move) throws InterruptedException {
showStatus("Applet rubik running..." + moves.length());
rotate(move);
if (moves.isempty()) {
showStatus("Applet rubik waiting");
phibase = 0;
phi = 0;
calcDrag = true;
repaint(1);
}
}
private int[] resize(int[] result, int[] points, int iconSize, int bound) {
for (int i = 0; i < points.length; i++) {
result[i] = points[i] * iconSize;
if (points[i] < 0) result[i] += bound;
}
return result;
}
private synchronized void rotate(queue move) throws InterruptedException {
twisting = false;
double phistop;
int quads = move.quads;
if (!naturalState && move.face == twistFace && move.mode == twistMode) {
phistop = Math.PI / 2 * quads - phibase;
quads &= 3;
} else {
phi = phibase = 0;
if (move.quads == 1) phistop = Math.PI / 2;
else if (move.quads == 3) phistop = -Math.PI / 2;
else if (Math.random() < 0.5) phistop = Math.PI;
else phistop = -Math.PI;
twistFace = move.face;
twistMode = move.mode;
}
if (av != 0) {
naturalState = false;
double av = this.av * 0.001;
long time = System.currentTimeMillis();
if (phistop > 0) while (phi < phistop) {
repaint(1);
wait();
phi = (System.currentTimeMillis() - time) * av;
} else while (phi > phistop) {
repaint(1);
wait();
phi = (time - System.currentTimeMillis()) * av;
}
}
naturalState = true;
if (quads != 0) {
colorTwist(faceCols, twistFace, quads, twistMode);
if (redo != null) redo.sub(twistFace, 4 - quads, twistMode, planes);
undo.sub(twistFace, 4 - quads, twistMode, planes);
}
}
private void rotate(vector eye, vector eye1, vector eye2, int dir, Event evt) {
double best = 0;
int face = -1;
for (int i = 0; i < 6; i++) {
double temp = eye.mult(faceVec[i]) * dir;
if (temp > best && temp > eye1.mult(faceVec[i]) * dir && temp > eye2.mult(faceVec[i]) * dir) {
best = temp;
face = i;
}
}
moves.add(new queue(face, evt.shiftDown() ? 1 : 3, planes > 2 && evt.controlDown() ? 1 : 0, planes));
}
public boolean rotateSide(int side, int degrees) {
if (side < 0 || side >= 6) return false;
double thetastop = Math.PI * degrees / 180;
if (this.av != 0) {
vector TeyeX = eyeX, TeyeY = eyeY, TeyeZ = eyeZ;
long time = System.currentTimeMillis();
Graphics g = getGraphics();
double av = this.av * 0.001;
for (;;) {
double theta;
if (thetastop < 0) {
theta = (time - System.currentTimeMillis()) * av;
if (theta <= thetastop) break;
} else {
theta = (System.currentTimeMillis() - time) * av;
if (theta >= thetastop) break;
}
eyeX = new vector(TeyeX);
eyeY = new vector(TeyeY);
eyeZ = new vector(TeyeZ);
rotateSide(eyeX, eyeY, eyeZ, side, theta);
paint(g);
}
g.dispose();
eyeX = TeyeX;
eyeY = TeyeY;
eyeZ = TeyeZ;
}
rotateSide(eyeX, eyeY, eyeZ, side, thetastop);
calcDrag = true;
repaint(1);
return true;
}
private void rotateSide(vector TeyeX, vector TeyeY, vector TeyeZ, int side, double theta) {
double Cphi = Math.cos(theta), Sphi = Math.sin(theta);
switch (side) {
case 0: // -x
TeyeZ.rotateX(Cphi, -Sphi);
TeyeY.rotateX(Cphi, -Sphi);
TeyeX.rotateX(Cphi, -Sphi);
break;
case 1: // y
TeyeZ.rotateY(Cphi, Sphi);
TeyeY.rotateY(Cphi, Sphi);
TeyeX.rotateY(Cphi, Sphi);
break;
case 2: // -z
TeyeZ.rotateZ(Cphi, -Sphi);
TeyeY.rotateZ(Cphi, -Sphi);
TeyeX.rotateZ(Cphi, -Sphi);
break;
case 3: // z
TeyeZ.rotateZ(Cphi, Sphi);
TeyeY.rotateZ(Cphi, Sphi);
TeyeX.rotateZ(Cphi, Sphi);
break;
case 4: // -y
TeyeZ.rotateY(Cphi, -Sphi);
TeyeY.rotateY(Cphi, -Sphi);
TeyeX.rotateY(Cphi, -Sphi);
break;
case 5: // x
TeyeZ.rotateX(Cphi, Sphi);
TeyeY.rotateX(Cphi, Sphi);
TeyeX.rotateX(Cphi, Sphi);
break;
}
}
public boolean rotateX(int degrees) {
if (degrees <= -90 || degrees >= 90) return false;
double thetastop = Math.PI * degrees / 180;
if (this.av != 0) {
vector TeyeY = eyeY, TeyeZ = eyeZ;
long time = System.currentTimeMillis();
Graphics g = getGraphics();
double av = this.av * 0.001;
for (;;) {
double theta;
if (thetastop < 0) {
theta = (time - System.currentTimeMillis()) * av;
if (theta <= thetastop) break;
} else {
theta = (System.currentTimeMillis() - time) * av;
if (theta >= thetastop) break;
}
eyeZ = new vector(TeyeZ);
eyeY = new vector(TeyeY);
eyeZ.addmultnorm(eyeY, Math.tan(theta));
eyeY.multnorm(eyeZ, eyeX);
paint(g);
}
eyeZ = TeyeZ;
eyeY = TeyeY;
}
eyeZ.addmultnorm(eyeY, Math.tan(thetastop));
eyeY.multnorm(eyeZ, eyeX);
calcDrag = true;
repaint(1);
return true;
}
public boolean rotateY(int degrees) {
if (degrees <= -90 || degrees >= 90) return false;
double thetastop = Math.PI * degrees / 180;
if (this.av != 0) {
vector TeyeZ = eyeZ, TeyeX = eyeX;
long time = System.currentTimeMillis();
Graphics g = getGraphics();
double av = this.av * 0.001;
for (;;) {
double theta;
if (thetastop < 0) {
theta = (time - System.currentTimeMillis()) * av;
if (theta <= thetastop) break;
} else {
theta = (System.currentTimeMillis() - time) * av;
if (theta >= thetastop) break;
}
eyeX = new vector(TeyeX);
eyeZ = new vector(TeyeZ);
eyeX.addmultnorm(eyeZ, Math.tan(theta));
eyeZ.multnorm(eyeX, eyeY);
paint(g);
}
eyeX = TeyeZ;
eyeZ = TeyeZ;
}
eyeX.addmultnorm(eyeZ, Math.tan(thetastop));
eyeZ.multnorm(eyeX, eyeY);
calcDrag = true;
repaint(1);
return true;
}
public boolean rotateZ(int degrees) {
if (degrees <= -90 || degrees >= 90) return false;
double thetastop = Math.PI * degrees / 180;
if (this.av != 0) {
vector TeyeX = eyeX, TeyeY = eyeY;
long time = System.currentTimeMillis();
Graphics g = getGraphics();
double av = this.av * 0.001;
for (;;) {
double theta;
if (thetastop < 0) {
theta = (time - System.currentTimeMillis()) * av;
if (theta <= thetastop) break;
} else {
theta = (System.currentTimeMillis() - time) * av;
if (theta >= thetastop) break;
}
eyeY = new vector(TeyeY);
eyeX = new vector(TeyeX);
eyeY.addmultnorm(eyeX, Math.tan(theta));
eyeX.multnorm(eyeY, eyeZ);
paint(g);
}
eyeY = TeyeY;
eyeX = TeyeX;
}
eyeY.addmultnorm(eyeX, Math.tan(thetastop));
eyeX.multnorm(eyeY, eyeZ);
calcDrag = true;
repaint(1);
return true;
}
public void run() {
try {
backX = -Integer.parseInt(getParameter("backx"));
backY = -Integer.parseInt(getParameter("backy"));
bkImage = getImage(getCodeBase(), getParameter("background"));
prepareImage(bkImage, this);
} catch (Exception e) {
}
showStatus(getCodeBase().getHost().endsWith("parkwaycc.co.uk") ? "Applet rubik waiting" : "Applet rubik © 1997-8 Neil Rashbrook");
try {
for (;;) perform(moves.remove());
} catch (Exception e) {
e.printStackTrace();
} finally {
phibase = phi = 0; // Finished
naturalState = true;
//rotator = null;
calcDrag = true;
repaint(1);
showStatus("Applet rubik stopped");
return;
}
}
public void setEye(double eyeXx, double eyeXy, double eyeXz, double eyeYx, double eyeYy, double eyeYz, double eyeZx, double eyeZy, double eyeZz) {
eyeX = new vector(eyeXx, eyeXy, eyeXz);
eyeY = new vector(eyeYx, eyeYy, eyeYz);
eyeZ = new vector(eyeZx, eyeZy, eyeZz);
repaint(1);
}
public void setEyeXY(double eyeXx, double eyeXy, double eyeXz, double eyeYx, double eyeYy, double eyeYz) {
eyeX = new vector(eyeXx, eyeXy, eyeXz);
eyeY = new vector(eyeYx, eyeYy, eyeYz);
eyeZ = new vector(eyeX, eyeY);
repaint(1);
}
public void setEyeYZ(double eyeXz, double eyeYx, double eyeYy, double eyeYz, double eyeZx, double eyeZy, double eyeZz) {
eyeY = new vector(eyeYx, eyeYy, eyeYz);
eyeZ = new vector(eyeZx, eyeZy, eyeZz);
eyeX = new vector(eyeY, eyeZ);
repaint(1);
}
public void setEyeZX(double eyeZx, double eyeZy, double eyeZz, double eyeXx, double eyeXy, double eyeXz) {
eyeZ = new vector(eyeZx, eyeZy, eyeZz);
eyeX = new vector(eyeXx, eyeXy, eyeXz);
eyeY = new vector(eyeZ, eyeX);
repaint(1);
}
public boolean setFacelet(int face, int row, int col, int colour) {
if (face >= 0 && face < 6 && row >= 0 && row < planes && col >= 0 && col < planes && colour >= 0 && colour < 8) {
faceCols[face][row][col] = colour;
repaint(1);
return true;
}
return false;
}
private void setPlanes(int planes) {
this.planes = planes;
sideBlock = sideBlocks[planes];
cornerOrder[2] = planes - 1;
cornerOrder[3] = planes - 1;
facelets = getParameter("facelets" + planes);
if (facelets != null && facelets.length() == planes * planes * 6) {
int l = 0;
char facechars[] = facelets.toCharArray();
for (int i = 0; i < 6; i++) for (int j = 0; j < planes; j++) for (int k = 0; k < planes; k++) {
faceCols[i][j][k] = facechars[l++] - '0';
if (faceCols[i][j][k] < 0 || faceCols[i][j][k] > 7) faceCols[i][j][k] = i;
}
} else {
facelets = null;
for (int i = 0; i < 6; i++) for (int j = 0; j < 4; j++) for (int k = 0; k < 4; k++) faceCols[i][j][k] = i;
}
undo.reset();
redo = null;
if (planes > 1) {
String s;
s = getParameter("moves" + planes);
if (s != null) addMoves(s, null);
s = getParameter("steps" + planes);
if (s != null) addMoves(s, redo = new queue());
}
twisting = false;
naturalState = true;
calcDrag = true;
repaint(1);
}
public void update(Graphics g) {
paint(g);
}
private void writeParameters() {
System.err.println("<applet code=" + getClass().getName() + ".class codebase=" + getCodeBase() + " width=" + bounds.width + " height=" + bounds.height + " alt=\"Your browser understands the &lt;applet&gt; tag but isn't running the applet, for some reason.\">");
eyeX.writeParameters("eyeX");
eyeY.writeParameters("eyeY");
eyeZ.writeParameters("eyeZ");
System.err.println("<param name=\"planes\" value=\"" + planes + "\">");
System.err.println("<param name=\"select\" value=\"" + select + "\">");
System.err.println("<param name=\"sticky\" value=\"" + sticky + "\">");
System.err.println("<param name=\"grabrect\" value=\"" + grabrect + "\">");
System.err.println("<param name=\"graball\" value=\"" + graball + "\">");
System.err.println("<param name=\"rotation\" value=\"" + av + "\">");
System.err.println("<param name=\"bgcolor\" value=\"" + Integer.toHexString(bgColor.getRGB()).substring(2) + "\">");
System.err.println("<param name=\"text\" value=\"" + Integer.toHexString(textColor.getRGB()).substring(2) + "\">");
System.err.println("<param name=\"link\" value=\"" + Integer.toHexString(disabledColor.getRGB()).substring(2) + "\">");
System.err.println("<param name=\"vlink\" value=\"" + Integer.toHexString(enabledColor.getRGB()).substring(2) + "\">");
System.err.println("<param name=\"alink\" value=\"" + Integer.toHexString(activeColor.getRGB()).substring(2) + "\">");
System.err.println("<param name=\"planes\" value=\"" + planes + "\">");
System.err.println("<param name=\"facelets" + planes + "\" value=\"" + getFacelets() + "\">");
if (!undo.isempty()) System.err.println("<param name=\"moves" + planes + "\" value=\"" + undo.moves(true) + "\">");
if (redo != null && !redo.isempty()) System.err.println("<param name=\"steps" + planes + "\" value=\"" + redo.moves(false) + "\">");
if (redo == null && !undo.isempty()) System.err.println("<param name=\"steps" + planes + "\" value=\"" + undo.moves(false) + "\">");
if ("true".equals(getParameter("focus"))) System.err.println("<param name=\"focus\" value=\"true\">");
if (bkImage != null) {
System.err.println("<param name=\"background\" value=\"" + getParameter("background") + "\">");
System.err.println("<param name=\"backx\" value=\"" + -backX + "\">");
System.err.println("<param name=\"backy\" value=\"" + -backY + "\">");
}
for (int i = 0; i < 6; i++) System.err.println("<param name=\"face" + i + "\" value=\"" + colors[colMap[i]] + "\">");
System.err.println("Your browser doesn't understand the &lt;applet&gt; tag.</applet>");
}
}
final class queue { //Class to describe a queue of moves
public final int face, quads, mode;
private queue next, prev;
private boolean waiting;
public queue() {
face = -1;
quads = -1;
mode = -1;
next = this;
prev = this;
waiting = true;
}
public queue(int face, int quads, int mode, int planes) {
if (mode + mode + face / 3 >= planes) {
face = 5 - face;
quads = 4 - quads;
mode = planes - mode - 1;
}
this.face = face;
this.quads = quads;
this.mode = mode;
next = null;
prev = null;
waiting = false;
}
private queue(queue src) {
//this(src.face, src.quads, src.mode);
this.face = src.face;
this.quads = src.quads;
this.mode = src.mode;
next = null;
prev = null;
waiting = false;
}
public synchronized void add(queue next) {
next.prev = this;
next.next = this.next;
this.next.prev = next;
this.next = next;
waiting = false;
notifyAll();
}
public synchronized void add(int face, int quads, int mode, int planes) {
if (mode + mode + face / 3 >= planes) {
face = 5 - face;
quads = 4 - quads;
mode = planes - mode - 1;
}
queue temp = next;
while (temp != this && (temp.face + face == 5 || temp.face == face && temp.mode != mode)) temp = temp.next;
if (temp.face == face && temp.mode == mode) {
quads = (quads + temp.quads) & 3;
temp.prev.next = temp.next;
temp.next.prev = temp.prev;
temp.next = null;
temp.prev = null;
}
if (quads != 0) add(new queue(face, quads, mode, planes));
}
public synchronized void sub(queue prev) {
prev.next = this;
prev.prev = this.prev;
this.prev.next = prev;
this.prev = prev;
waiting = false;
notifyAll();
}
public synchronized void sub(int face, int quads, int mode, int planes) {
if (mode + mode + face / 3 >= planes) {
face = 5 - face;
quads = 4 - quads;
mode = planes - mode - 1;
}
queue temp = prev;
while (temp != this && (temp.face + face == 5 || temp.face == face && temp.mode != mode)) temp = temp.prev;
if (temp.face == face && temp.mode == mode) {
quads = (quads + temp.quads) & 3;
temp.next.prev = temp.prev;
temp.prev.next = temp.next;
temp.prev = null;
temp.next = null;
}
if (quads != 0) sub(new queue(face, quads, mode, planes));
}
public synchronized int length() {
int count = 0;
for (queue temp = this.prev; temp != this; temp = temp.prev) count++;
return count;
}
private synchronized boolean copy2(queue dest, boolean all) {
queue temp = this.prev;
if (temp == this) return false;
do dest.add(new queue(temp));
while (all && (temp = temp.prev) != this);
return true;
}
public synchronized boolean append(queue src, boolean all) {
if (waiting && src != null && src.copy2(this, all)) {
notifyAll();
return true;
}
return false;
}
private synchronized boolean send2(queue dest) {
if (prev == this) return false;
next.prev = dest.prev;
dest.prev.next = next;
next = null;
prev.next = dest;
dest.prev = prev;
prev = null;
return true;
}
public synchronized boolean insert(queue src) {
return src.send2(this);
}
public synchronized queue remove() throws InterruptedException {
while (prev == this) {
waiting = true;
wait();
}
queue result = prev;
this.prev = prev.prev;
this.prev.next = this;
result.next = null;
result.prev = null;
notifyAll();
return result;
}
private synchronized boolean matches(queue src) {
return prev.face == src.face && prev.quads == src.quads && prev.mode == src.mode;
}
public synchronized boolean startsWith(queue src) {
return src.matches(prev);
}
public synchronized boolean iswaiting() {
return waiting;
}
public synchronized boolean isempty() {
return prev == this;
}
public synchronized boolean isnearempty() {
return prev == next;
}
private static char faces[] = {'F', 'U', 'L', 'R', 'D', 'B'};
private static char quad[] = {'\0', '+', '|', '-'};
private static char quadr[] = {'\0', '-', '|', '+'};
public synchronized String moves(boolean reversed) {
StringBuffer sb = new StringBuffer();
if (reversed) for (queue temp = next; temp != this; temp = temp.next) {
sb.append(faces[temp.face]);
sb.append(quadr[temp.quads]);
if (temp.mode != 0) sb.append('%');
} else for (queue temp = prev; temp != this; temp = temp.prev) {
sb.append(faces[temp.face]);
sb.append(quad[temp.quads]);
if (temp.mode != 0) sb.append('%');
}
return sb.toString();
}
public synchronized void reset() {
next = this;
prev = this;
waiting = true;
}
}
final class vector { // Class to manipulate a 3D vector
private double x, y, z;
public vector() { // Empty vector
x = 0;
y = 0;
z = 0;
}
public vector(double x, double y, double z) { // Create a vector
this.x = x;
this.y = y;
this.z = z;
}
public vector(vector v) { // Create a copy of a vector
x = v.x;
y = v.y;
z = v.z;
}
public vector(vector v1, vector v2) { // Create a vector by vector product
mult(v1, v2);
norm();
}
public double mult(vector v) { // Scalar product
return x * v.x + y * v.y + z * v.z;
}
private double norm2() { // Square of length
return x * x + y * y + z * z;
}
public double cosAng(vector v) { // Cosine of angle between vectors
return mult(v) / Math.sqrt(norm2() * v.norm2());
}
private void norm() { // Normalize vector
double d = Math.sqrt(norm2());
x /= d;
y /= d;
z /= d;
}
public void add(vector v) { // Add vector
x += v.x;
y += v.y;
z += v.z;
}
public void addmult(vector v, double d) { // Add scaled vector
x += v.x * d;
y += v.y * d;
z += v.z * d;
}
public void addmultnorm(vector v, double d) { // With normalization
addmult(v, d);
norm();
}
public void sub(vector v) { // Subtract vector
x -= v.x;
y -= v.y;
z -= v.z;
}
public void copy(vector v) { // Copy a vector
x = v.x;
y = v.y;
z = v.z;
}
private void mult(vector v1, vector v2) { // Vector product
x = v1.y * v2.z - v1.z * v2.y;
y = v1.z * v2.x - v1.x * v2.z;
z = v1.x * v2.y - v1.y * v2.x;
}
public void multnorm(vector v1, vector v2) { // With normalization
mult(v1, v2);
norm();
}
public void rotateX(double cos, double sin) { // Rotation about X axis
double y = this.y * cos + z * sin;
z = z * cos - this.y * sin;
this.y = y;
}
public void rotateY(double cos, double sin) { // Rotation about Y axis
double x = this.x * cos + z * sin;
z = z * cos - this.x * sin;
this.x = x;
}
public void rotateZ(double cos, double sin) { // Rotation about Z axis
double x = this.x * cos + y * sin;
y = y * cos - this.x * sin;
this.x = x;
}
private static String parameter(String param, char suffix, double d) {
if (d <= -1 || d >= 1 || d == 0) return "<param name=\"" + param + suffix + "\" value=\"" + d + "\">";
else if (d < 0) return "<param name=\"" + param + suffix + "\" value=\"-" + String.valueOf(10 - d).substring(1) + "\">";
else return "<param name=\"" + param + suffix + "\" value=\"" + String.valueOf(10 + d).substring(1) + "\">";
}
public void writeParameters(String param) {
System.err.println(parameter(param, 'x', x));
System.err.println(parameter(param, 'y', y));
System.err.println(parameter(param, 'z', z));
}
}
final class paral extends java.awt.Polygon { // Class to describe a parallelogram
private static int[] coords(double d, double dh, double dv, double left, double top) {
// Convert vectors to corner coordinates for single coloured square
return new int[] {
(int)(d + dh * (left + 0.1) + dv * (top + 0.1)),
(int)(d + dh * (left + 0.9) + dv * (top + 0.1)),
(int)(d + dh * (left + 0.9) + dv * (top + 0.9)),
(int)(d + dh * (left + 0.1) + dv * (top + 0.9)),
(int)(d + dh * (left + 0.1) + dv * (top + 0.1))};
}
private static int[] coords(double d, double dh, double dv, rect r) {
// Convert vectors to corner coordinates for black face given by rect
return new int[] {
(int)(d + dh * r.left + dv * r.top),
(int)(d + dh * r.right + dv * r.top),
(int)(d + dh * r.right + dv * r.bottom),
(int)(d + dh * r.left + dv * r.bottom)};
}
private static int[] coords(double d, double dh, double dv, rect r, boolean b) {
// Convert vectors to corner coordinates for drag region given by rect
int[] result = new int[4];
if (b) {
result[0] = (int)(d + dh * r.left + dv * (r.top + 0.1));
result[1] = (int)(d + dh * r.right + dv * (r.top + 0.1));
result[2] = (int)(d + dh * r.right + dv * (r.bottom - 0.1));
result[3] = (int)(d + dh * r.left + dv * (r.bottom - 0.1));
} else {
result[0] = (int)(d + dh * (r.left + 0.1) + dv * r.top);
result[1] = (int)(d + dh * (r.right - 0.1) + dv * r.top);
result[2] = (int)(d + dh * (r.right - 0.1) + dv * r.bottom);
result[3] = (int)(d + dh * (r.left + 0.1) + dv * r.bottom);
}
return result;
}
private static double[][] l = {null, null, {0, 1, 2, 2, 1, 0}, {0, 1, 2, 3, 3, 2, 1, 0}, {0, 1, 2, 3, 4, 4, 3, 2, 1, 0}};
private static double[] all = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
private static double[] trim = {0.1, 0.9, 0.1, 0.9, 0.1, 0.9, 0.1, 0.9, 0.1, 0.9};
private static int[] coords(double d, double dh, double dv, rect r, int p, boolean g, boolean b) {
// Convert vectors to corner coordinates for drag region given by rect
double[] s = g ? all : trim;
double[] x = b ? l[p] : s;
double[] y = b ? s : l[p];
int[] result = new int[p + p + 2];
for (int i = 0; i < result.length; i++) result[i] = (int)(d + dh * (r.left + x[i]) + dv * (r.top + y[i]));
return result;
}
public paral(double x, double y, double dxh, double dyh, double dxv, double dyv, int left, int top) {
super(coords(x, dxh, dxv, left, top), coords(y, dyh, dyv, left, top), 5); // coloured square
}
public paral(double x, double y, double dxh, double dyh, double dxv, double dyv, rect r) {
super(coords(x, dxh, dxv, r), coords(y, dyh, dyv, r), 4); // black face
}
public paral(double x, double y, double dxh, double dyh, double dxv, double dyv, rect r, boolean b) {
super(coords(x, dxh, dxv, r, b), coords(y, dyh, dyv, r, b), 4); // drag region
}
public paral(double x, double y, double dxh, double dyh, double dxv, double dyv, rect r, int p, boolean g, boolean b) {
super(coords(x, dxh, dxv, r, p, g, b), coords(y, dyh, dyv, r, p, g, b), p + p + 2); // drag region
}
}
final class facelet { // Class to store facelet positions
public final facelet next;
public final int face, row, col;
public final paral poly;
public facelet(facelet next, int face, int row, int col, paral poly) {
this.next = next;
this.face = face;
this.row = row;
this.col = col;
this.poly = poly;
}
}
final class drag { // Class to store drag data to see if a twist should occur
public final drag next;
public final double dirX, dirY;
public final int mode, face;
public final paral poly;
public drag(drag next, double dirX, double dirY, int mode, int face, paral poly) {
this.next = next; // Next drag data in chain
this.dirX = dirX; // Drag direction for twist
this.dirY = dirY; // (used to calculate angle of twist)
this.mode = mode; // Whether face or middle slice
this.face = face; // Which face to twist
this.poly = poly; // Region in which dragging can start
}
}
final class corn { // Class to store corners associated with a face of the cube
public final int nw, ne, sw, bk;
public corn(int nw, int ne, int sw, int bk) {
this.nw = nw; // Northwest corner
this.ne = ne; // Northeast corner, due east of nw corner
this.sw = sw; // Southwest corner, due south of nw corner
this.bk = bk; // Northwest corner, due behind nw corner
}
}
final class rect { // Class to describe a rectangle á la Windows RECT
public final int left, top, right, bottom;
public rect(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}

Frames Version
Customization Page
Back to Rubik's Cube Page


619655
38.107.191.96