Skip to content

Instantly share code, notes, and snippets.

@Bolloxim
Created August 3, 2014 06:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bolloxim/63c96e281d85b76d90c1 to your computer and use it in GitHub Desktop.
Save Bolloxim/63c96e281d85b76d90c1 to your computer and use it in GitHub Desktop.
A Pen by Andi Smithers.

Asteriod Field

Taking the icosahedron we deform the vertices randomly for each asteriod giving a unique shape to each one. We then give it some rotational component and then collide them with each other to create a nice bouncing action.

version 0.2 allow morphing greater a safe value which now can cause concave issues.

version 0.3 timer to spawn a new random field. Tied into the same structures as the long range scanner.

A Pen by Andi Smithers on CodePen.

License.

<body>
<canvas id='asteroid field 3d'></canvas>
</body>
// constants
const focalDepth = 80;
const focalPoint = 256;
const maxAsteriods = 32;
var localSpaceCubed = 10;
// variables
var centreX;
var centreY;
var mouseX;
var mouseY;
var spawnX;
var spawnY;
var frameCount=0;
var shieldUp;
var splutterCount=0;
var splutterNoise=60;
var spawnTime;
// matrix
var orientation = new matrix3x3();
// basic matrices 3x3
function matrix3x3()
{
// set as identiy
this.m = [1,0,0,0,1,0,0,0,1];
}
matrix3x3.prototype.clone = function(matrix)
{
this.m = matrix.m.slice(0);
}
matrix3x3.prototype.rotateX = function(angle)
{
var c = Math.cos(angle);
var s = Math.sin(angle);
this.m = [1,0,0,0,c,-s,0, s, c];
}
matrix3x3.prototype.rotateY = function(angle)
{
var c = Math.cos(angle);
var s = Math.sin(angle);
this.matrix = [c,0,s,0,1,0,-s, 0, c];
}
matrix3x3.prototype.rotateZ = function(angle)
{
var c = Math.cos(angle);
var s = Math.sin(angle);
this.m = [c,-s,0,s,c,0,0,0,1];
}
matrix3x3.prototype.multiply = function(matrix)
{
var m1 = this.m;
var m2 = matrix.m;
return {m:[m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6],
m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7],
m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8],
m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6],
m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7],
m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8],
m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6],
m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7],
m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8]] };
}
matrix3x3.prototype.transform = function(tx, ty, tz)
{
return {x:tx*this.m[0]+ty*this.m[1]+tz*this.m[2],
y:tx*this.m[3]+ty*this.m[4]+tz*this.m[5],
z:tx*this.m[6]+ty*this.m[7]+tz*this.m[8]};
}
// asteriods
var asteriods = [];
function SetupAsteriods()
{
asteriods = [];
for (var i=0; i<maxAsteriods; i++)
{
asteriods.push(new Asteriod())
}
var d = new Date();
spawnTime = d.getTime() + 30000;
}
function RenderAsteriods()
{
for (var i=0; i<asteriods.length;i++)
{
asteriods[i].render();
}
}
function UpdateAsteriods()
{
for (var i=0; i<asteriods.length;i++)
{
asteriods[i].update();
}
// should really broadphase but 32x32 checks is not awful
for (var i=0; i<asteriods.length;i++)
{
var src = asteriods[i];
for (var j=i+1; j<asteriods.length;j++)
{
var dst = asteriods[j];
var dx = dst.x-src.x;
var dy = dst.y-src.y;
var dz = dst.z-src.z;
if (dx*dx + dy*dy + dz*dz < 2)
{
dst.angVel.y*=-1;
dst.angVel.p*=-1;
src.angVel.y*=-1;
src.angVel.p*=-1;
}
}
}
}
function Asteriod()
{
this.init();
}
Asteriod.prototype.init = function()
{
this.asteroidVerts = [];
this.x = Math.random()*localSpaceCubed-localSpaceCubed*0.5;
this.y = Math.random()*localSpaceCubed-localSpaceCubed*0.5;
this.z = Math.random()*localSpaceCubed-localSpaceCubed*0.5;
this.angVel = {y:(Math.random()-0.5)*Math.PI/120, p:(Math.random()-0.5)*Math.PI/120, r:(Math.random()-0.5)*Math.PI/120};
this.rotation = new matrix3x3();
// fetch the icosahedron data and morph it
for (var i=0; i< icosahedronVerts.length; i++)
{
var deform = Math.random()*1.0-0.5;
var x = icosahedronVerts[i].x + icosahedronVerts[i].x * deform;
var y = icosahedronVerts[i].y + icosahedronVerts[i].y * deform;
var z = icosahedronVerts[i].z + icosahedronVerts[i].z * deform;
this.asteroidVerts[i] = {x:x, y:y, z:z};
}
}
Asteriod.prototype.update = function()
{
var matrixY = new matrix3x3();
var matrixP = new matrix3x3();
var matrixR = new matrix3x3();
matrixY.rotateZ(this.angVel.y);
matrixP.rotateX(this.angVel.p);
matrixR.rotateY(this.angVel.r);
this.rotation.clone(this.rotation.multiply(matrixR.multiply(matrixP.multiply(matrixY))));
this.x += this.angVel.p;
this.y += this.angVel.r;
this.z += this.angVel.y;
}
Asteriod.prototype.render = function()
{
var scale = 512/canvas.height;
var transforms = [];
var cphi = Math.cos(phi);
var sphi = Math.sin(phi);
var ctheta = Math.cos(theta);
var stheta = Math.sin(theta);
// transform into 3d coords
for (var i=0; i<this.asteroidVerts.length; i++)
{
var vert = this.asteroidVerts[i];
var t = this.rotation.transform(vert.x, vert.y, vert.z);
/* var tz = vert.z * ctheta + vert.y * stheta;
var ty = vert.y * ctheta - vert.z * stheta;
// rotate phi
var tx = vert.x * cphi - ty * sphi;
ty = ty * cphi + vert.x * sphi;
*/
t.x +=this.x;
t.y +=this.y;
t.z +=this.z;
// pixel depth using a long focal distance to ensure all set is in focus
var depth = focalPoint*5 / ((t.z + 5*scale) +1);
if (depth<=0) return;
var x1 = t.x*depth+centreX;
var y1 = t.y*depth+centreY;
transforms.push( {x:x1, y:y1, z:t.z} );
}
for (var i=0; i<icosahedronTris.length;i+=3)
{
var a = icosahedronTris[i];
var b = icosahedronTris[i+1];
var c = icosahedronTris[i+2];
// back face cull
var dx1 = transforms[b].x - transforms[a].x;
var dy1 = transforms[b].y - transforms[a].y;
var dx2 = transforms[b].x - transforms[c].x;
var dy2 = transforms[b].y - transforms[c].y;
var cross = (dx1*dy2 - dy1*dx2);
if (cross < 0) continue;
// use cross product normal to reflect light
var shade = Math.floor(Math.sqrt(cross)*scale)+10;
// fill the triangle
context.beginPath();
context.moveTo(transforms[a].x, transforms[a].y);
context.lineTo(transforms[b].x, transforms[b].y);
context.lineTo(transforms[c].x, transforms[c].y);
context.closePath();
context.fillStyle = 'rgba('+shade+','+shade+','+shade+',1)';
context.fill();
// edge it in red for fun
context.lineWidth = 1;
context.strokeStyle = 'rgba('+(shade>>1)+','+(shade>>1)+','+(shade>>1)+', 1)';
context.stroke();
}
}
// test multiple groups
var icosahedronVerts = [];
var icosahedronTris = [];
function buildVerts()
{
icosahedronVerts =
[
{x: 0.000, y: 0.000, z: 1.000},
{x: 0.894, y: 0.000, z: 0.447},
{x: 0.276, y: 0.851, z: 0.447},
{x:-0.724, y: 0.526, z: 0.447},
{x:-0.724, y:-0.526, z: 0.447},
{x: 0.276, y:-0.851, z: 0.447},
{x: 0.724, y: 0.526, z:-0.447},
{x:-0.276, y: 0.851, z:-0.447},
{x:-0.894, y: 0.000, z:-0.447},
{x:-0.276, y:-0.851, z:-0.447},
{x: 0.724, y:-0.526, z:-0.447},
{x: 0.000, y: 0.000, z:-1.000}
];
icosahedronTris =
[
0,1,2,
0,2,3,
0,3,4,
0,4,5,
0,5,1,
11,7,6,
11,8,7,
11,9,8,
11,10,9,
11,6,10,
2,1,6,
2,7,3,
3,8,4,
4,9,5,
5,10,1,
6,7,2,
7,8,3,
8,9,4,
9,10,5,
10,6,1
];
}
// initialization
function init()
{
// setup canvas and context
canvas = document.getElementById('asteroid field 3d');
context = canvas.getContext('2d');
// set canvas to be window dimensions
resize();
// create event listeners
canvas.addEventListener('mousemove', mouseMove);
canvas.addEventListener('click', mouseClick);
window.addEventListener('resize', resize);
// initialze variables
buildVerts();
// build asteroidds
SetupAsteriods();
}
// input functions
function mouseMove(event)
{
var rect = canvas.getBoundingClientRect();
mouseX = event.clientX - rect.left,
mouseY = event.clientY - rect.top
}
function mouseClick()
{
shieldUp^=1;
splutterCount = 30;
splutterNoise = 60;
}
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// compute centre of screen
centreX = canvas.width/2;
centreY = canvas.height/2;
}
// rendering functions
var phi = Math.PI*0.5;
var theta = 0;
function renderIcosahedron()
{
var scale = 512/canvas.height;
phi+=0.009;
theta+=0.01;
var transforms = [];
var cphi = Math.cos(phi);
var sphi = Math.sin(phi);
var ctheta = Math.cos(theta);
var stheta = Math.sin(theta);
// transform into 3d coords
for (var i=0; i<icosahedronVerts.length; i++)
{
var vert = icosahedronVerts[i];
var tz = vert.z * ctheta + vert.y * stheta;
var ty = vert.y * ctheta - vert.z * stheta;
// rotate phi
var tx = vert.x * cphi - ty * sphi;
ty = ty * cphi + vert.x * sphi;
// pixel depth using a long focal distance to ensure all set is in focus
var depth = focalPoint*5 / ((tz + 5*scale) +1);
var x1 = tx*depth+centreX;
var y1 = ty*depth+centreY;
transforms.push( {x:x1, y:y1, z:tz} );
}
for (var i=0; i<icosahedronTris.length;i+=3)
{
var a = icosahedronTris[i];
var b = icosahedronTris[i+1];
var c = icosahedronTris[i+2];
// back face cull
var dx1 = transforms[b].x - transforms[a].x;
var dy1 = transforms[b].y - transforms[a].y;
var dx2 = transforms[b].x - transforms[c].x;
var dy2 = transforms[b].y - transforms[c].y;
var cross = (dx1*dy2 - dy1*dx2);
if (cross < 0) continue;
// use cross product normal to reflect light
var shade = Math.floor(Math.sqrt(cross)*scale)+10;
// fill the triangle
context.beginPath();
context.moveTo(transforms[a].x, transforms[a].y);
context.lineTo(transforms[b].x, transforms[b].y);
context.lineTo(transforms[c].x, transforms[c].y);
context.closePath();
context.fillStyle = 'rgba('+shade+','+shade+','+shade+',1)';
context.fill();
// edge it in red for fun
context.lineWidth = 2;
context.strokeStyle = 'rgba('+(shade>>4)+','+(shade>>4)+','+(shade>>4)+', 1)';
context.stroke();
}
}
var spin =0;
function renderShield()
{
var phi = Math.PI*0.5;
var theta = spin;
var transforms = [];
var cphi = Math.cos(phi);
var sphi = Math.sin(phi);
var ctheta = Math.cos(theta);
var stheta = Math.sin(theta);
// transform into 3d coords
for (var i=0; i<icosahedronVerts.length; i++)
{
var vert = icosahedronVerts[i];
var tz = vert.z * ctheta + vert.y * stheta;
var ty = vert.y * ctheta - vert.z * stheta;
// rotate phi
var tx = vert.x * cphi - ty * sphi;
ty = ty * cphi + vert.x * sphi;
// pixel depth using a long focal distance to ensure all set is in focus
var depth = 1200 / ((tz + 0.2*256/canvas.height) +1);
var x1 = tx*depth+centreX;
var y1 = ty*depth+centreY;
transforms.push( {x:x1, y:y1, z:tz} );
}
for (var i=0; i<icosahedronTris.length;i+=3)
{
var a = icosahedronTris[i];
var b = icosahedronTris[i+1];
var c = icosahedronTris[i+2];
// back face cull
var dx1 = transforms[b].x - transforms[a].x;
var dy1 = transforms[b].y - transforms[a].y;
var dx2 = transforms[b].x - transforms[c].x;
var dy2 = transforms[b].y - transforms[c].y;
var cross = (dx1*dy2 - dy1*dx2);
if (cross > 0) continue;
// use cross product normal to reflect light
var shade = Math.floor(Math.sqrt(-cross));
// fill the triangle
context.beginPath();
context.moveTo(transforms[a].x, transforms[a].y);
context.lineTo(transforms[b].x, transforms[b].y);
context.lineTo(transforms[c].x, transforms[c].y);
context.closePath();
context.fillStyle = 'rgba('+0+','+shade+','+0+',0.25)';
context.fill();
// edge it in red for fun
context.lineWidth = 1;
context.strokeStyle = 'rgba('+0+','+shade+','+0+', 0.1)';
context.stroke();
}
}
function render()
{
context.fillStyle = 'black';
context.clearRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation='source-over';
RenderAsteriods();
context.globalCompositeOperation='lighter';
splutterNoise++;
spin+=0.01;
if (splutterCount) splutterCount--;
var splutter = splutterNoise&splutterCount;
if ( shieldUp & !splutter) renderShield();
context.globalCompositeOperation='source-over';
context.globalAlpha = 1.0;
context.font = '20pt Calibri';
context.fillStyle = 'rgb(255,255,255)';
context.textAlign = "center";
context.fillText('Asteriod Field', canvas.width/2, 20);
context.font = '14pt Calibri';
context.fillText('(mouse button to Shield Up)', canvas.width/2, 40);
var d = new Date();
var currentTime = d.getTime();
context.font = '14pt Calibri';
context.fillText('respawn in ' + Math.floor((spawnTime - currentTime)/1000), canvas.width/2, canvas.height-10);
}
// movement functions
function update()
{
var d = new Date();
var currentTime = d.getTime();
if ( spawnTime - currentTime <0) SetupAsteriods();
UpdateAsteriods();
}
// per frame tick functions
function animate()
{
frameCount++;
// movement update
update();
// render update
render();
// trigger next frame
requestAnimationFrame(animate);
}
// entry point
init();
animate();
body{
background:#000000
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment