// Rubik's Cube 3D simulator
// Karl Hörnell, March 11 1996
// Last modified March 30
import java.awt.*;
import java.lang.Math;
public final class rubik extends java.applet.Applet
{
int i,j,k,n,o,p,q,lastX,lastY,dx,dy;
int rectX[],rectY[];
Color colList[];
final double sideVec[]={0,0,1,0,0,-1,0,-1,0,1,0,0,0,1,0,-1,0,0};
final double corners[]={-1,-1,-1,1,-1,-1,1,1,-1,-1,1,-1,
-1,-1,1,1,-1,1,1,1,1,-1,1,1};
double topCorners[],botCorners[];
final int sides[]={4,5,6,7,3,2,1,0,0,1,5,4,1,2,6,5,2,3,7,6,0,4,7,3};
final int nextSide[]={2,3,4,5, 4,3,2,5, 1,3,0,5, 1,4,0,2, 1,5,0,3, 2,0,4,1};
final int mainBlocks[]={0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3};
final int twistDir[]={-1,1,-1,1, -1,1,-1,1, 1,1,1,1, 1,-1,1,-1, 1,1,1,1, -1,1,-1,1};
final int colDir[]={-1,-1,1,-1,1,-1};
final int circleOrder[]={0,1,2,5,8,7,6,3};
int topBlocks[],botBlocks[];
int sideCols[],sideW,sideH;
int dragReg,twistSide=-1;
int nearSide[],buffer[]; // Which side belongs to dragCorn
double dragCorn[],dragDir[];
double eye[]={0.3651,0.1826,-0.9129};
double eX[]={0.9309,-0.0716,0.3581};
double eY[]={0,0.9806,0.1961};
double Teye[],TeX[],TeY[];
double light[],temp[]={0,0,0},temp2[]={0,0,0},newCoord[];
double sx,sy,sdxh,sdyh,sdxv,sdyv,d,t1,t2,t3,t4,t5,t6;
double phi,phibase=0,Cphi,Sphi,currDragDir[];
boolean naturalState=true,twisting=false,OKtoDrag=false;
Math m;
Graphics offGraphics;
Image offImage;
public void init()
{
offImage=createImage(120,120);
offGraphics=offImage.getGraphics();
rectX=new int[4];
rectY=new int[4];
newCoord=new double[16];
dragDir=new double[24];
dragCorn=new double[96];
topCorners=new double[24];
botCorners=new double[24];
topBlocks=new int[24];
botBlocks=new int[24];
buffer=new int[12];
nearSide=new int[12];
light=new double[3];
Teye=new double[3];
TeX=new double[3];
TeY=new double[3];
currDragDir=new double[2];
vecProd(eye,0,eX,0,eY,0);
normalize(eY,0);
colList=new Color[120];
for (i=0;i<20;i++)
{
colList[i]=new Color(103+i*8,103+i*8,103+i*8); // White
colList[i+20]=new Color(i*6,i*6,84+i*9); // Blue
colList[i+40]=new Color(84+i*9,i*5,i*5); // Red
colList[i+60]=new Color(i*6,84+i*9,i*6); // Green
colList[i+80]=new Color(84+i*9,84+i*9,i*6); // Yellow
colList[i+100]=new Color(84+i*9,55+i*8,i*3); // Orange
}
sideCols=new int[54];
for (i=0;i<54;i++)
sideCols[i]=i/9;
resize(125,125);
repaint();
}
public double scalProd(double v1[],int ix1,double v2[],int ix2)
{
return v1[ix1]*v2[ix2]+v1[ix1+1]*v2[ix2+1]+v1[ix1+2]*v2[ix2+2];
}
public double vNorm(double v[],int ix)
{
return m.sqrt(v[ix]*v[ix]+v[ix+1]*v[ix+1]+v[ix+2]*v[ix+2]);
}
public double cosAng(double v1[],int ix1,double v2[],int ix2)
{
return scalProd(v1,ix1,v2,ix2)/(vNorm(v1,ix1)*vNorm(v2,ix2));
}
public void normalize(double v[], int ix)
{
double t=vNorm(v,ix);
v[ix]=v[ix]/t;
v[ix+1]=v[ix+1]/t;
v[ix+2]=v[ix+2]/t;
}
public void scalMult(double v[], int ix,double a)
{
v[ix]=v[ix]*a;
v[ix+1]=v[ix+1]*a;
v[ix+2]=v[ix+2]*a;
}
public void addVec(double v1[], int ix1,double v2[],int ix2)
{
v2[ix2]+=v1[ix1];
v2[ix2+1]+=v1[ix1+1];
v2[ix2+2]+=v1[ix1+2];
}
public void subVec(double v1[], int ix1,double v2[],int ix2)
{
v2[ix2]-=v1[ix1];
v2[ix2+1]-=v1[ix1+1];
v2[ix2+2]-=v1[ix1+2];
}
public void copyVec(double v1[], int ix1,double v2[],int ix2)
{
v2[ix2]=v1[ix1];
v2[ix2+1]=v1[ix1+1];
v2[ix2+2]=v1[ix1+2];
}
public void vecProd(double v1[],int ix1,double v2[],int ix2,
double v3[],int ix3)
{
v3[ix3]=v1[ix1+1]*v2[ix2+2]-v1[ix1+2]*v2[ix2+1];
v3[ix3+1]=v1[ix1+2]*v2[ix2]-v1[ix1]*v2[ix2+2];
v3[ix3+2]=v1[ix1]*v2[ix2+1]-v1[ix1+1]*v2[ix2];
}
public void cutUpCube()
{
boolean check;
for (i=0;i<24;i++) // Copy main coordinate data
{
topCorners[i]=corners[i];
botCorners[i]=corners[i];
}
copyVec(sideVec,3*twistSide,temp,0); // Start manipulating and build new parts
copyVec(temp,0,temp2,0);
scalMult(temp,0,1.3333);
scalMult(temp2,0,0.6667);
for (i=0;i<8;i++)
{
check=false;
for (j=0;j<4;j++)
if (i==sides[twistSide*4+j])
check=true;
if (check)
subVec(temp2,0,botCorners,i*3);
else
addVec(temp,0,topCorners,i*3);
}
for (i=0;i<24;i++) // Fix the subsquare blockings. First copy data from main
{
topBlocks[i]=mainBlocks[i];
botBlocks[i]=mainBlocks[i];
}
for (i=0;i<6;i++)
{
if (i==twistSide)
{
botBlocks[i*4+1]=0;
botBlocks[i*4+3]=0;
}
else
{
k=-1;
for (j=0;j<4;j++)
if (nextSide[i*4+j]==twistSide)
k=j;
switch (k) // Twisted side adjacent to...
{
case 0: // Up side?
{
topBlocks[i*4+3]=1;
botBlocks[i*4+2]=1;
break;
}
case 1: // Right side?
{
topBlocks[i*4]=2;
botBlocks[i*4+1]=2;
break;
}
case 2: // Down side?
{
topBlocks[i*4+2]=2;
botBlocks[i*4+3]=2;
break;
}
case 3: // Left side?
{
topBlocks[i*4+1]=1;
botBlocks[i*4]=1;
break;
}
case -1: // None
{
topBlocks[i*4+1]=0;
topBlocks[i*4+3]=0;
break;
}
}
}
}
}
public boolean keyDown(java.awt.Event evt, int key)
{
if (key==114)
{
twisting=false;
naturalState=true;
for (i=0;i<54;i++)
sideCols[i]=i/9;
repaint();
}
else if (key==115)
{
twisting=false;
naturalState=true;
for (i=0;i<20;i++)
colorTwist((int)(m.random()*6),(int)(m.random()*3+1));
repaint();
}
return false;
}
public boolean mouseDrag(java.awt.Event evt, int x, int y)
{
boolean check;
double x1,x2,y1,y2,alpha,beta;
if ((!twisting)&&(OKtoDrag))
{
OKtoDrag=false;
check=false;
for (i=0;i0)&&(alpha<1)&&(beta>0)&&(beta<1))
{
currDragDir[0]=dragDir[i*2];
currDragDir[1]=dragDir[i*2+1];
d=currDragDir[0]*(x-lastX)+currDragDir[1]*(y-lastY);
d=d*d/((currDragDir[0]*currDragDir[0]+currDragDir[1]*currDragDir[1])*
((x-lastX)*(x-lastX)+(y-lastY)*(y-lastY)));
if (d>0.6)
{
check=true;
twistSide=nearSide[i];
i=100;
}
}
}
if (check)
{
if (naturalState)
{
cutUpCube();
naturalState=false;
}
twisting=true;
phi=0.02*(currDragDir[0]*(x-lastX)+currDragDir[1]*(y-lastY))/
m.sqrt(currDragDir[0]*currDragDir[0]+currDragDir[1]*currDragDir[1]);
repaint();
return false;
}
}
OKtoDrag=false;
if (!twisting) // Normal turning
{
dx=lastX-x;
copyVec(eX,0,temp,0);
scalMult(temp,0,((double)dx)*0.016);
addVec(temp,0,eye,0);
vecProd(eY,0,eye,0,eX,0);
normalize(eX,0);
normalize(eye,0);
dy=y-lastY;
copyVec(eY,0,temp,0);
scalMult(temp,0,((double)dy)*0.016);
addVec(temp,0,eye,0);
vecProd(eye,0,eX,0,eY,0);
normalize(eY,0);
normalize(eye,0);
lastX=x;
lastY=y;
repaint();
}
else // Twist
{
phi=0.02*(currDragDir[0]*(x-lastX)+currDragDir[1]*(y-lastY))/
m.sqrt(currDragDir[0]*currDragDir[0]+currDragDir[1]*currDragDir[1]);
repaint();
}
return false;
}
public boolean mouseDown(java.awt.Event evt, int x, int y)
{
lastX=x;
lastY=y;
OKtoDrag=true;
return false;
}
public boolean mouseUp(java.awt.Event evt, int x, int y)
{
int quads;
double qu;
if (twisting)
{
twisting=false;
phibase+=phi;
phi=0;
qu=phibase;
while (qu<0)
qu+=125.662;
quads=((int)(qu*3.183));
if (((quads % 5)==0)||((quads % 5)==4))
{
quads=((quads+1)/5) % 4;
if (colDir[twistSide]<0)
quads=(4-quads) % 4;
phibase=0;
naturalState=true;
colorTwist(twistSide,quads);
}
repaint();
}
return false;
}
public void colorTwist(int sideNum, int quads)
{
int i,j,k,l=0;
k=quads*2;
for (i=0;i<8;i++)
{
buffer[k]=sideCols[sideNum*9+circleOrder[i]];
k=(k+1) % 8;
}
for (i=0;i<8;i++)
sideCols[sideNum*9+circleOrder[i]]=buffer[i];
k=quads*3;
for (i=0;i<4;i++)
{
for (j=0;j<4;j++)
if (nextSide[nextSide[sideNum*4+i]*4+j]==sideNum)
l=j;
for (j=0;j<3;j++)
{
switch(l)
{
case 0:
buffer[k]=sideCols[nextSide[sideNum*4+i]*9+j];
break;
case 1:
buffer[k]=sideCols[nextSide[sideNum*4+i]*9+2+3*j];
break;
case 2:
buffer[k]=sideCols[nextSide[sideNum*4+i]*9+8-j];
break;
case 3:
buffer[k]=sideCols[nextSide[sideNum*4+i]*9+6-3*j];
break;
default:
break;
}
k=(k+1) % 12;
}
}
k=0;
for (i=0;i<4;i++)
{
for (j=0;j<4;j++)
if (nextSide[nextSide[sideNum*4+i]*4+j]==sideNum)
l=j;
for (j=0;j<3;j++)
{
switch(l)
{
case 0:
sideCols[nextSide[sideNum*4+i]*9+j]=buffer[k];
break;
case 1:
sideCols[nextSide[sideNum*4+i]*9+2+3*j]=buffer[k];
break;
case 2:
sideCols[nextSide[sideNum*4+i]*9+8-j]=buffer[k];
break;
case 3:
sideCols[nextSide[sideNum*4+i]*9+6-3*j]=buffer[k];
break;
default:
break;
}
k++;
}
}
}
public void paint(Graphics g)
{
dragReg=0;
offGraphics.setColor(Color.lightGray);
offGraphics.fillRect(0,0,120,120);
if (naturalState)
fixBlock(eye,eX,eY,corners,mainBlocks,0);
else
{
copyVec(eye,0,Teye,0);
copyVec(eX,0,TeX,0);
Cphi=m.cos(phi+phibase);
Sphi=-m.sin(phi+phibase);
switch(twistSide)
{
case 0: // z
Teye[0]=Cphi*eye[0]+Sphi*eye[1];
TeX[0]=Cphi*eX[0]+Sphi*eX[1];
Teye[1]=-Sphi*eye[0]+Cphi*eye[1];
TeX[1]=-Sphi*eX[0]+Cphi*eX[1];
break;
case 1: // -z
Teye[0]=Cphi*eye[0]-Sphi*eye[1];
TeX[0]=Cphi*eX[0]-Sphi*eX[1];
Teye[1]=Sphi*eye[0]+Cphi*eye[1];
TeX[1]=Sphi*eX[0]+Cphi*eX[1];
break;
case 2: // -y
Teye[0]=Cphi*eye[0]-Sphi*eye[2];
TeX[0]=Cphi*eX[0]-Sphi*eX[2];
Teye[2]=Sphi*eye[0]+Cphi*eye[2];
TeX[2]=Sphi*eX[0]+Cphi*eX[2];
break;
case 3: // x
Teye[1]=Cphi*eye[1]+Sphi*eye[2];
TeX[1]=Cphi*eX[1]+Sphi*eX[2];
Teye[2]=-Sphi*eye[1]+Cphi*eye[2];
TeX[2]=-Sphi*eX[1]+Cphi*eX[2];
break;
case 4: // y
Teye[0]=Cphi*eye[0]+Sphi*eye[2];
TeX[0]=Cphi*eX[0]+Sphi*eX[2];
Teye[2]=-Sphi*eye[0]+Cphi*eye[2];
TeX[2]=-Sphi*eX[0]+Cphi*eX[2];
break;
case 5: // -x
Teye[1]=Cphi*eye[1]-Sphi*eye[2];
TeX[1]=Cphi*eX[1]-Sphi*eX[2];
Teye[2]=Sphi*eye[1]+Cphi*eye[2];
TeX[2]=Sphi*eX[1]+Cphi*eX[2];
break;
default:
break;
}
vecProd(Teye,0,TeX,0,TeY,0);
if (scalProd(eye,0,sideVec,twistSide*3)<0) // Top facing away? Draw it first
{
fixBlock(Teye,TeX,TeY,topCorners,topBlocks,2);
fixBlock(eye,eX,eY,botCorners,botBlocks,1);
}
else
{
fixBlock(eye,eX,eY,botCorners,botBlocks,1);
fixBlock(Teye,TeX,TeY,topCorners,topBlocks,2);
}
}
g.drawImage(offImage,0,0,this);
}
public void update(Graphics g)
{
paint(g);
}
public void fixBlock(double beye[],double beX[],double beY[],
double bcorners[],int bblocks[],int mode)
{
copyVec(beye,0,light,0);
scalMult(light,0,-3);
addVec(beX,0,light,0);
subVec(beY,0,light,0);
for (i=0;i<8;i++)
{
newCoord[i*2]=(60+35.1*scalProd(bcorners,i*3,beX,0));
newCoord[i*2+1]=(60-35.1*scalProd(bcorners,i*3,beY,0));
}
for (i=0;i<6;i++)
{
if (scalProd(beye,0,sideVec,3*i)>0.001)
{
k=(int)(9.6*(1-cosAng(light,0,sideVec,3*i)));
offGraphics.setColor(Color.black);
for (j=0;j<4;j++)
{
rectX[j]=(int)newCoord[2*sides[i*4+j]];
rectY[j]=(int)newCoord[2*sides[i*4+j]+1];
}
offGraphics.fillPolygon(rectX,rectY,4);
sideW=bblocks[i*4+1]-bblocks[i*4];
sideH=bblocks[i*4+3]-bblocks[i*4+2];
if (sideW>0)
{
sx=newCoord[2*sides[i*4]];
sy=newCoord[2*sides[i*4]+1];
sdxh=(newCoord[2*sides[i*4+1]]-sx)/sideW;
sdxv=(newCoord[2*sides[i*4+3]]-sx)/sideH;
sdyh=(newCoord[2*sides[i*4+1]+1]-sy)/sideW;
sdyv=(newCoord[2*sides[i*4+3]+1]-sy)/sideH;
p=bblocks[i*4+2];
for (n=0;n0))
{
if (sideW==3) // Determine positive drag direction
if (bblocks[i*4+2]==0)
{
dragDir[dragReg*2]=sdxh*twistDir[i*4];
dragDir[dragReg*2+1]=sdyh*twistDir[i*4];
}
else
{
dragDir[dragReg*2]=-sdxh*twistDir[i*4+2];
dragDir[dragReg*2+1]=-sdyh*twistDir[i*4+2];
}
else
if (bblocks[i*4]==0)
{
dragDir[dragReg*2]=-sdxv*twistDir[i*4+3];
dragDir[dragReg*2+1]=-sdyv*twistDir[i*4+3];
}
else
{
dragDir[dragReg*2]=sdxv*twistDir[i*4+1];
dragDir[dragReg*2+1]=sdyv*twistDir[i*4+1];
}
for (j=0;j<4;j++)
{
dragCorn[dragReg*8+j]=newCoord[2*sides[i*4+j]];
dragCorn[dragReg*8+4+j]=newCoord[2*sides[i*4+j]+1];
}
nearSide[dragReg]=twistSide;
dragReg++;
}
break;
default:
break;
}
}
}
}
}