// 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;i<dragReg;i++) // Check if inside drag region			{				x1=dragCorn[i*8+1]-dragCorn[i*8];				x2=dragCorn[i*8+5]-dragCorn[i*8+4];				y1=dragCorn[i*8+3]-dragCorn[i*8];				y2=dragCorn[i*8+7]-dragCorn[i*8+4];				alpha=(y2*(lastX-dragCorn[i*8])-y1*(lastY-dragCorn[i*8+4]))/					(x1*y2-y1*x2);				beta=(-x2*(lastX-dragCorn[i*8])+x1*(lastY-dragCorn[i*8+4]))/					(x1*y2-y1*x2);				if ((alpha>0)&&(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;n<sideH;n++)					{						q=bblocks[i*4];						for (o=0;o<sideW;o++)						{							rectX[0]=(int)(sx+(o+0.1)*sdxh+(n+0.1)*sdxv);							rectX[1]=(int)(sx+(o+0.9)*sdxh+(n+0.1)*sdxv);							rectX[2]=(int)(sx+(o+0.9)*sdxh+(n+0.9)*sdxv);							rectX[3]=(int)(sx+(o+0.1)*sdxh+(n+0.9)*sdxv);							rectY[0]=(int)(sy+(o+0.1)*sdyh+(n+0.1)*sdyv);							rectY[1]=(int)(sy+(o+0.9)*sdyh+(n+0.1)*sdyv);							rectY[2]=(int)(sy+(o+0.9)*sdyh+(n+0.9)*sdyv);							rectY[3]=(int)(sy+(o+0.1)*sdyh+(n+0.9)*sdyv);							offGraphics.setColor(colList[20*sideCols[i*9+p*3+q]+k]);							offGraphics.fillPolygon(rectX,rectY,4);							q++;						}						p++;					}				}				switch (mode)				{					case 0: // Just the normal cube						t1=sx;						t2=sy;						t3=sdxh;						t4=sdyh;						t5=sdxv;						t6=sdyv;						for (j=0;j<4;j++)						{							dragCorn[8*dragReg]=t1;							dragCorn[8*dragReg+4]=t2;							dragCorn[8*dragReg+3]=t1+t5;							dragCorn[8*dragReg+7]=t2+t6;							t1=t1+t3*3;							t2=t2+t4*3;							dragCorn[8*dragReg+1]=t1;							dragCorn[8*dragReg+5]=t2;							dragCorn[8*dragReg+2]=t1+t5;							dragCorn[8*dragReg+6]=t2+t6;							dragDir[dragReg*2]=t3*twistDir[i*4+j];							dragDir[dragReg*2+1]=t4*twistDir[i*4+j];							d=t3;							t3=t5;							t5=-d;							d=t4;							t4=t6;							t6=-d;							nearSide[dragReg]=nextSide[i*4+j];							dragReg++;						}						break;					case 1: // The 2/3-cube						break;					case 2: // The top						if ((i!=twistSide)&&(sideW>0))						{							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;				}			}		}	}}
