// Rubik's Cube 3D simulator // Neil Rashbrook August 18 1997 // Last modified May 19 1998 import java.awt.*; public final class demo extends java.applet.Applet implements Runnable { private Thread rotator = null; private int planes, av, pause; 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 Insets sideBlock[][]; private static final Insets sideBlock1[][] = {{null, null, null, null, null, new Insets(0, 0, 1, 1)}}; private static final Insets sideBlock2[][] = { // Partial faces to draw twisted cube {new Insets(0, 0, 1, 2), new Insets(0, 1, 2, 2), new Insets(1, 0, 2, 2), new Insets(0, 0, 2, 1), null, new Insets(0, 0, 2, 2)}, {new Insets(1, 0, 2, 2), new Insets(0, 0, 2, 1), new Insets(0, 0, 1, 2), new Insets(0, 1, 2, 2), new Insets(0, 0, 2, 2), null}}; private static final Insets sideBlock3[][] = { // Partial faces to draw twisted cube {new Insets(0, 0, 1, 3), new Insets(0, 2, 3, 3), new Insets(2, 0, 3, 3), new Insets(0, 0, 3, 1), null, new Insets(0, 0, 3, 3)}, {new Insets(1, 0, 2, 3), new Insets(0, 1, 3, 2), new Insets(1, 0, 2, 3), new Insets(0, 1, 3, 2), null, null}, {new Insets(2, 0, 3, 3), new Insets(0, 0, 3, 1), new Insets(0, 0, 1, 3), new Insets(0, 2, 3, 3), new Insets(0, 0, 3, 3), null}}; private static final Insets sideBlock4[][] = { // Partial faces to draw twisted cube {new Insets(0, 0, 1, 4), new Insets(0, 3, 4, 4), new Insets(3, 0, 4, 4), new Insets(0, 0, 4, 1), null, new Insets(0, 0, 4, 4)}, {new Insets(1, 0, 2, 4), new Insets(0, 2, 4, 3), new Insets(2, 0, 3, 4), new Insets(0, 1, 4, 2), null, null}, {new Insets(2, 0, 3, 4), new Insets(0, 1, 4, 2), new Insets(1, 0, 2, 4), new Insets(0, 2, 4, 3), null, null}, {new Insets(3, 0, 4, 4), new Insets(0, 0, 4, 1), new Insets(0, 0, 1, 4), new Insets(0, 3, 4, 4), new Insets(0, 0, 4, 4), null}}; private static final Insets 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 int twistFace = 0, twistMode = 2; // Currently twisting block private vector eyeX, eyeY, eyeZ; private double phi, phibase = 0, currDragX, currDragY; private boolean naturalState = true, doubleClick = false; private boolean 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 = -3, mode = 0; switch (chars[i++]) { default: continue; case 'F': quads = -1; case 'f': face = 0; break; case 'U': case 'T': quads = -1; case 'u': case 't': face = 1; break; case 'L': quads = -1; case 'l': face = 2; break; case 'R': quads = -1; case 'r': face = 3; break; case 'D': quads = -1; case 'd': face = 4; break; case 'B': quads = -1; case 'b': face = 5; break; } if (i < chars.length) switch (chars[i]) { case '1': case 'o': case '+': case '>': case '`': case ']': case '}': case '¹': quads = 3; i++; break; case '2': case 'i': case '.': case '=': case '\"': case '\\': case '|': case '²': quads = 2; i++; break; case '3': case 'a': case '-': 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 ? -quads : quads, mode); else if (quads < 0) redo.add(face, -quads, mode, planes); else redo.add(new queue(face, quads, mode, planes)); } } 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; } } 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, Insets r) { // Convert vectors to corner coordinates for black face given by Insets 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)}; } 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); Polygon p; 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]; Insets 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(p = new Polygon(coords(sx, sdxh, sdxv, sideBlock[0][5]), coords(sy, sdyh, sdyv, sideBlock[0][5]), 4)); offGraphics.drawPolygon(p); } else { // First draw blank offGraphics.setColor(Color.black); offGraphics.fillPolygon(p = new Polygon(coords(sx, sdxh, sdxv, block), coords(sy, sdyh, sdyv, block), 4)); 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++) { offGraphics.setColor(thisList[colMap[thisRowCols[l]]]); offGraphics.fillPolygon(p = new Polygon(coords(sx, sdxh, sdxv, l, k), coords(sy, sdyh, sdyv, l, k), 5)); offGraphics.setColor(thisList[10]); offGraphics.drawPolygon(p); } } } } } 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"}, {"link", "000000-FFFFFF", "Arrow body color"}, {"moves2", "String", "2x2x2 initial moves"}, {"moves3", "String", "3x3x3 initial moves"}, {"moves4", "String", "4x4x4 initial moves"}, {"pause", "int", "Sleep delay between moves"}, {"planes", "int", "Initial cube size (default 3)"}, {"rotation", "int", "1-3 radians per second, 0 jumps [default 2]"}, {"steps2", "String", "2x2x2 script steps"}, {"steps3", "String", "3x3x3 script steps"}, {"steps4", "String", "4x4x4 script steps"}, {"text", "000000-FFFFFF", "Arrow border color"}, {"vlink", "000000-FFFFFF", "Arrow unused color"}}; } private int getPause() { try { return Math.max(0, Integer.parseInt(getParameter("pause"))); } catch (Exception e) { return 0; } } 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.nrr.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(); pause = getPause(); select = getParameter("select"); if (select == null) select = "1234"; copyright = "Neil Rashbrook".equalsIgnoreCase(getParameter("copyright")); setBackground(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()); } 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; } 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 1: // a if (evt.controlDown()) av = evt.shiftDown() ? 0 : 1; else av = evt.shiftDown() ? 3 : 2; return true; case 3: // c if (moves.iswaiting()) setPlanes(planes); // Clear return true; case 8: // h home(); return true; case 16: // p plain = evt.shiftDown(); repaint(1); return true; case 27: // ESC if (moves.reset()) showStatus("Applet rubik stopping..."); return true; } return false; } public boolean mouseDown(Event evt, int x, int y) { 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; lastX = x; lastY = y; if (!moves.iswaiting() && cursorFrame != null) cursorFrame.setCursor(Frame.DEFAULT_CURSOR); } return true; } public boolean mouseDrag(Event evt, int x, int y) { doubleClick = false; int dx = x - lastX, dy = y - lastY; // 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) { cursorFrame.setCursor(nCursor); } return true; } public boolean mouseUp(Event evt, int x, int y) { if (moves.iswaiting()) { 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 (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); } } } 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); repaint(1); if (!moves.isempty()) Thread.sleep(pause); else { showStatus("Applet rubik waiting"); phibase = 0; phi = 0; } } 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 { 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); } } 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); 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); 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); 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); 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; 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()); } naturalState = true; repaint(1); } public void start() { if ("true".equals(getParameter("focus"))) try { requestFocus(); // for appletviewer 1.1 } catch (Exception e) { } } public void update(Graphics g) { paint(g); } }