M=Math,s=[],c=[],a=[],x=y=r=95,m=n=i=0;
while(i<361){s[i]=M.sin(j=i*M.PI/180);c[i++]=M.cos(j)}for(i=0;i<12;i++){for(j=0;j<18;j++){a.push([{x:(E=r*s[C=i*15])*(H=c[D=j*20]),y:E*(G=s[D]),z:F=r*c[C],c:(i+j)%2?'#C00':'#CCC'},{x:(I=r*s[A=C+15])*H,y:I*G,z:J=r*c[A]},{x:I*(L=c[B=D+20]),y:I*(K=s[B]),z:J},{x:E*L,y:E*K,z:F}])}}
i=document.getElementById('c');v=i.getContext('2d');i.width=570;i.height=430;
function z(){v.fillStyle='#148';v.fillRect(0,0,570,430);v.fillStyle='#39C';for(i=0;i<12;i++){for(j=i%2;j<16;j+=2){v.fillRect(j*35+5,i*35+5,35,35)}};N=s[P=x%360];O=c[P];v.fillStyle='rgba(0,0,0,.3)';v.arc(x+30,y+9,r,3,9,0);v.fill();b=[];for(i in a){k=[];for(j in a[i]){f=a[i][j];X=f.x*O-f.y*N;Y=f.x*N+f.y*O;Z=f.z*c[A=170]-X*s[A];k.push({x:f.z*s[A]+X*c[A]+x,y:Y*O-Z*N+y,z:Y*N+Z*O,c:f.c})}b.push(k)}b.sort(function(a,b){return(a[0].z-b[0].z)});for(i in b){f=b[i];v.fillStyle=f[0].c;v.beginPath();for(j in f)v.lineTo(f[j].x,f[j].y);v.fill()}if((x+=m)>469)m=-1;if(x-r<6)m=1;if((y+=n)>329)n=-1;if(y-r<6)n=1;setTimeout(z,1)}
z()
// Set up sin & cos tables
var sin=[], cos=[];
for (var angle=0; angle<361; angle++) {
var angleRads = angle * Math.PI / 180;
sin[angle] = Math.sin(angleRads);
cos[angle] = Math.cos(angleRads);
}
// Set up ball data
var ballData = [];
var radius = 95;
var xSteps = 18;
var ySteps = 12;
var xAngle = 360 / xSteps
var yAngle = 180 / ySteps;
for (y=0; y<ySteps; y++) {
for (x=0; x<xSteps; x++) {
var x1 = x * xAngle;
var y1 = y * yAngle;
var x2 = x1 + xAngle;
var y2 = y1 + yAngle;
var sx1 = sin[x1];
var cx1 = cos[x1];
var sy1 = sin[y1];
var cy1 = cos[y1];
var sx2 = sin[x2];
var cx2 = cos[x2];
var sy2 = sin[y2];
var cy2 = cos[y2];
ballData.push([
{
x: radius * sy1 * cx1,
y: radius * sy1 * sx1,
z: radius * cy1,
colour: (x + y) % 2 ? '#CC0000' : '#CCCCCC'
},{
x: radius * sy2 * cx1,
y: radius * sy2 * sx1,
z: radius * cy2
},{
x: radius * sy2 * cx2,
y: radius * sy2 * sx2,
z: radius * cy2
},{
x: radius * sy1 * cx2,
y: radius * sy1 * sx2,
z: radius * cy1
}
])
}
}
// Set up canvas
var squaresAcross = 16;
var squaresDown = 12;
var squareWidth = 35;
var squareHeight = 35;
var borderWidth = 5;
var borderHeight = 5;
var canvasWidth = squaresAcross * squareWidth + borderWidth * 2;
var canvasHeight = squaresDown * squareHeight + borderHeight * 2;
var canvasEl = document.getElementById('c');
var ctx = canvasEl.getContext('2d');
canvasEl.width = canvasWidth;
canvasEl.height = canvasHeight;
// Draw chequerboard background
ctx.fillStyle = '#114488';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = '#3399CC';
for (y=0; y<squaresDown; y++) {
for (x=y%2; x<squaresAcross; x+=2) {
ctx.fillRect(x * squareWidth + borderWidth, y * squareHeight + borderHeight, squareWidth, squareHeight);
}
};
var bg = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// Set up ball position and movement variables
var ballX = radius + borderWidth;
var ballY = radius + borderHeight;
var xInc = yInc = 1;
// Convert 3D coordinates to 2D, taking into account rotation angle
function map3dCoordsTo2d(vertex) {
x = vertex.x * cosX - vertex.y * sinX;
y = vertex.x * sinX + vertex.y * cosX;
z = vertex.z * cos[170] - x * sin[170];
return ({
x: vertex.z * sin[170] + x * cos[170] + ballX,
y: y * cosX - z * sinX + ballY,
z: y * sinX + z * cosX,
colour: vertex.colour
});
}
// Z-sorts 2 faces
function zSort(a, b) {
var aHighestZ = Math.max(a[0].z, a[1].z);
var bHighestZ = Math.max(b[0].z, b[1].z);
return (aHighestZ - bHighestZ);
}
// Blats the background, draws the ball
function render() {
// Update rotation angles
sinX = sin[ballX % 360];
cosX = cos[ballX % 360];
// Clear background
ctx.putImageData(bg, 0, 0);
// Draw shadow
ctx.globalAlpha = 0.3;
ctx.fillStyle = '#000000';
ctx.arc(ballX + radius / 3, ballY + radius / 10, radius, 0, Math.PI * 2, 0);
ctx.fill();
ctx.globalAlpha = 1;
// Map 3D to 2D coordinates, cull invisible faces, and z-sort remaining faces
var sortedBallData = [];
for (var faceLoop=0; faceLoop<ballData.length; faceLoop++) {
var face = ballData[faceLoop];
var mappedFace = [];
for (vertexLoop=0; vertexLoop<face.length; vertexLoop++) {
var mappedVertex = map3dCoordsTo2d(face[vertexLoop]);
mappedFace.push(mappedVertex);
}
if (mappedFace[0].z < 0 && mappedFace[1].z < 0 && mappedFace[2].z < 0 && mappedFace[3].z < 0) continue;
sortedBallData.push(mappedFace);
}
sortedBallData.sort(zSort);
for (var faceLoop=0; faceLoop<sortedBallData.length; faceLoop++) {
var face = sortedBallData[faceLoop];
ctx.fillStyle = face[0].colour;
ctx.beginPath();
ctx.moveTo(face[0].x, face[0].y);
for (vertexLoop=1; vertexLoop<face.length; vertexLoop++) {
ctx.lineTo(face[vertexLoop].x, face[vertexLoop].y);
}
ctx.closePath();
ctx.fill();
}
ballX += xInc;
ballY += yInc;
if (ballX + radius > canvasWidth - borderWidth) xInc = -1;
if (ballX - radius == borderWidth) xInc = 1;
if (ballY + radius > canvasHeight - borderHeight) yInc = -1;
if (ballY - radius == borderHeight) yInc = 1;
setTimeout(render, 1);
}
render();