Math.dist2D=function(x1,y1,x2,y2){return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))} Math.dist3D=function(x1,y1,z1,x2,y2,z2){return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2))} var cells=[];//Will hold all the vertices. Why 'cells'? I plan to use this to make cellular automata. var chunks=[];//Will be used to optimize cell interactions var tris=[];//Will hold groups of 3 vertives which form triangles. //Cells on sphere. There is code for cellular automata in here. class Cell { constructor(x,y,z,setup) { this.x=x; this.y=y; this.z=z; this.nbors=[]; this.color=`rgb(${Math.floor(256*Math.random())}, ${Math.floor(256*Math.random())}, ${Math.floor(256*Math.random())})`;//Random color if(typeof setup=='function'){setup(this);}//Function for setup. } static automaton(array) { for(var ind1=0;ind1<array.length;ind1++){array[ind1].rule()} for(var ind1=0;ind1<array.length;ind1++){array[ind1].update()} } static setupRPS(obj) {//Sets up cell to be in cellular automata obj.state0=Math.floor(Math.random()*3); obj.state1=0; obj.rule=function() { var rps=[0,0,0]; for(var ind2=0;ind2<this.nbors.length;ind2++) { rps[this.nbors[ind2].state0]++ } if ((rps[this.state0+1-3*Math.floor((this.state0+1)/3)])>1){this.state1=this.state0+1-3*Math.floor((this.state0+1)/3)} else{this.state1=this.state0} } obj.update=function() { this.state0=this.state1; this.color=`hsl(${120*this.state0},100%,50%)`; } } static setupVector(obj) { var mag=Math.sqrt(obj.x*obj.x+obj.y*obj.y);//magnitude x/y var cosH=obj.x/mag; var sinH=obj.y/mag; if(mag==0){cosH=1;sinH=0} var cosV=obj.z; var sinV=mag; var theta=Math.random()*Math.PI*2; //Unit vector in a flat plane var x0=Math.cos(theta); var y0=Math.sin(theta); var z0=0; //Move direction of the vector var x1=(x0*cosV+z0*sinV)*cosH-y0*sinH//Cartesian coordinates transformed by origin var y1=(x0*cosV+z0*sinV)*sinH+y0*cosH; var z1=z0*cosV-x0*sinV; //Creates vector in 3d space obj.vec={x:x1,y:y1,z:z1} obj.dot=function(x,y,z){return ((x-this.x)*this.vec.x+(y-this.y)*this.vec.y+(z-this.z)*this.vec.z)} } static setupTerra(obj) { obj.gradients=[]; obj.worleys=[]; obj.height=0; obj.color=`hsl(0,0%,50%)`; obj.evalGradient=function(array) { var total=0; var rad=16/(array.length);//Radius square for falloff curve for(var ind=0;ind<array.length;ind++) { var vect=array[ind]; var dist=(this.x-vect.x)*(this.x-vect.x)+(this.y-vect.y)*(this.y-vect.y)+(this.z-vect.z)*(this.z-vect.z); var dot=vect.dot(this.x,this.y,this.z); total+=dot/Math.exp(11*dist*dist/rad/rad); } this.gradients.push(total); return total; } obj.evalWorley=function(array) { var dists=[2,2,2]; for(var ind=0;ind<array.length;ind++); { var point=array[ind]; var dist=Math.dist3D(this.x,this.y,this.z,point.x,point.y,point.z); if(dist<dists[0]) { dists[2]=dists[1]; dists[1]=dists[0]; dists[0]=dist; } else if(dist<dists[1]) { dists[2]=dists[1]; dists[1]=dist; } else if(dist<dists[2]) { dists[2]=dist; } } this.worleys.push(dists); return dists; } } static vectorMap(size) { var array=Cell.icosahedron(size,Cell.setupVector) return array; } static icosahedron(size,setup) {//Creates cells arranged in an icosahedron var array=[]; var cardinals=[[0,0,1], [Math.sqrt(3)*Math.cos(Math.PI/2.5)/2, Math.sqrt(3)*Math.sin(Math.PI/2.5)/2,1/2], [Math.sqrt(3)*Math.cos(2*Math.PI/2.5)/2, Math.sqrt(3)*Math.sin(2*Math.PI/2.5)/2,1/2], [Math.sqrt(3)*Math.cos(3*Math.PI/2.5)/2, Math.sqrt(3)*Math.sin(3*Math.PI/2.5)/2,1/2], [Math.sqrt(3)*Math.cos(4*Math.PI/2.5)/2, Math.sqrt(3)*Math.sin(4*Math.PI/2.5)/2,1/2], [Math.sqrt(3)*Math.cos(5*Math.PI/2.5)/2, Math.sqrt(3)*Math.sin(5*Math.PI/2.5)/2,1/2], [Math.sqrt(3)*Math.cos(1.5*Math.PI/2.5)/2,Math.sqrt(3)*Math.sin(1.5*Math.PI/2.5)/2,-1/2], [Math.sqrt(3)*Math.cos(2.5*Math.PI/2.5)/2,Math.sqrt(3)*Math.sin(2.5*Math.PI/2.5)/2,-1/2], [Math.sqrt(3)*Math.cos(3.5*Math.PI/2.5)/2,Math.sqrt(3)*Math.sin(3.5*Math.PI/2.5)/2,-1/2], [Math.sqrt(3)*Math.cos(4.5*Math.PI/2.5)/2,Math.sqrt(3)*Math.sin(4.5*Math.PI/2.5)/2,-1/2], [Math.sqrt(3)*Math.cos(5.5*Math.PI/2.5)/2,Math.sqrt(3)*Math.sin(5.5*Math.PI/2.5)/2,-1/2], [0,0,-1]]; var diamonds=[[1,0,6,2],[2,0,7,3],[3,0,8,4],[4,0,9,5],[5,0,10,1], [6,2,11,7],[7,3,11,8],[8,4,11,9],[9,5,11,10],[10,1,11,6]]; for(var aa=0;aa<10;aa++) { var point0=cardinals[diamonds[aa][0]]; var point1=cardinals[diamonds[aa][1]]; var point2=cardinals[diamonds[aa][2]]; var point3=cardinals[diamonds[aa][3]]; for(var bb=0;bb<size;bb++) { for(var cc=0;cc<size;cc++) { var coord1=(size-Math.max(bb,cc))/size; var coord2=(bb-cc)/size; var coord3=1-coord1-Math.abs(coord2); if(coord2>=0) {array.push(new Cell((coord1*point0[0]+coord2*point1[0]+coord3*point3[0]), (coord1*point0[1]+coord2*point1[1]+coord3*point3[1]), (coord1*point0[2]+coord2*point1[2]+coord3*point3[2]), setup));} else{array.push(new Cell((coord1*point0[0]-coord2*point2[0]+coord3*point3[0]), (coord1*point0[1]-coord2*point2[1]+coord3*point3[1]), (coord1*point0[2]-coord2*point2[2]+coord3*point3[2]), setup));} } } } array.push(new Cell(0,0,1,setup),new Cell(0,0,-1,setup)) //Identifies neighbors for(var ind1=0;ind1<array.length-1;ind1++) { var cell1=array[ind1]; for(var ind2=ind1+1;ind2<array.length;ind2++) { var cell2=array[ind2]; if(((cell1.x-cell2.x)*(cell1.x-cell2.x)+(cell1.y-cell2.y)*(cell1.y-cell2.y)+(cell1.z-cell2.z)*(cell1.z-cell2.z))<=(1.2*1.2/size/size)) { cell1.nbors.push(cell2); cell2.nbors.push(cell1); } } } for(var ind1=0;ind1<array.length;ind1++) { var cell1=array[ind1]; var dist=Math.sqrt(cell1.x*cell1.x+cell1.y*cell1.y+cell1.z*cell1.z); cell1.x/=dist;cell1.y/=dist;cell1.z/=dist; } for(var ind1=0;ind1<array.length;ind1++)//Arranges the vertices of the cell polygons { var cell1=array[ind1]; var vertSort=[]; //Angles for conversion var distH=Math.sqrt(cell1.x*cell1.x+cell1.y*cell1.y); var sinH=-cell1.y/distH; var cosH=cell1.x/distH; for(var ind2=0;ind2<cell1.nbors.length;ind2++) { var vertex=cell1.nbors[ind2]; var nx=-vertex.z*distH+(vertex.x*cosH-vertex.y*sinH)*cell1.z; var ny=vertex.x*sinH+vertex.y*cosH; vertSort.push([vertex,Math.atan2(ny,nx)]); } vertSort.sort(function(a,b){return(a[1]-b[1])}); cell1.nbors=[]; for(var ind2=0;ind2<vertSort.length;ind2++) { cell1.nbors.push(vertSort[ind2][0]); } cell1.updateVerts() } return array; } static distribute(chunkArr,density,setup) { var array=[];//Will hold all cells for(var ind1=0;ind1<chunkArr.length;ind1++) {chunkArr[ind1].contents=[];//Initializes chunk arrays chunkArr[ind1].checked=false} var dist1=2/Math.sqrt(chunkArr.length)//Distance between chunk points var dist2=3/Math.sqrt(chunkArr.length*density)//Distance between cell points var arc=2*Math.asin(dist1/2);//Maximum angle from chunk of a new point for(var ind1=0;ind1<chunkArr.length;ind1++)//Cycles all chunks { //Variables relevant to the chunk var chunk1=chunkArr[ind1]; var mag=Math.sqrt(chunk1.x*chunk1.x+chunk1.y*chunk1.y);//magnitude x/y var cosH=chunk1.x/mag; var sinH=chunk1.y/mag; if(mag==0){cosH=1;sinH=0} var cosV=chunk1.z; var sinV=mag; //Places random points until it can’t var tries=0;//How many failed tries to place a point while(tries<128) { //Generates the initial coordinatesof a random point var thetaH=Math.PI*2*Math.random();//Speherical coordinates var thetaV=arc*Math.sqrt(Math.random()); var x0=Math.cos(thetaH)*Math.sin(thetaV);//Initial cartesian coordinates var y0=Math.sin(thetaH)*Math.sin(thetaV); var z0=Math.cos(thetaV); var x1=(x0*cosV+z0*sinV)*cosH-y0*sinH//Cartesian coordinates transformed by chunk var y1=(x0*cosV+z0*sinV)*sinH+y0*cosH; var z1=z0*cosV-x0*sinV; //Checks this point against all other points in the neighborhood var canAdd=true; for(var ind2=-1;ind2<chunk1.nbors.length;ind2++) {//Cycles all chunks in neighborhood var chunk2=chunk1; if (ind2>0){chunk2=chunk1.nbors[ind2]} for(var ind3=0;ind3<chunk2.contents.length;ind3++) {//Cycles all other pointsin neighbiorhood var cell1=chunk2.contents[ind3]; if(Math.dist3D(x1,y1,z1,cell1.x,cell1.y,cell1.z)<dist2) {//Checks if new point is too close to existing points. If so, ends the loops. canAdd=false; ind3=chunk2.contents.length; ind2=chunk1.nbors.length; tries++; } } } //If the point isn’t too close to any others, adds it to the cell array if(canAdd) { var cell1=new Cell(x1,y1,z1,setup); array.push(cell1); //Puts cell into a chunk var inChunk=chunk1;//First chunk to try var checkDist1=dist1;//Distance from centrral chunk for(var ind2=0;ind2<chunk1.nbors.length;ind2++) { var chunk2=chunk1.nbors[ind2]; var checkDist2=Math.dist3D(x1,y1,z1,chunk2.x,chunk2.y,chunk2.z) if(checkDist2<checkDist1) {//If chunk2 iscloser than inChunk, updates checkDist1=checkDist2; inChunk=chunk2; } } inChunk.contents.push(cell1);//Puts cell into chunk contents } } } return array; } static setChunks(chunkArr,cellArr) { var chunkRad=3.6/(chunkArr.length-2); for(var ind1=0;ind1<chunkArr.length;ind1++) {//Clears chunk arrays chunkArr[ind1].contents=[]; chunkArr[ind1].checked=false; } for(var ind1=0;ind1<cellArr.length;ind1++) {//Adds all cells to nearest chunk var cell1=cellArr[ind1]; var dist1=8;var chunk1=null; for(var ind2=0;ind2<chunkArr.length;ind2++) { var chunk2=chunkArr[ind2]; var dist2=((cell1.x-chunk2.x)*(cell1.x-chunk2.x)+(cell1.y-chunk2.y)*(cell1.y-chunk2.y)+(cell1.z-chunk2.z)*(cell1.z-chunk2.z)); if(dist2<dist1) { dist1=dist2; chunk1=chunk2; //if(dist2<chunkRad){break} } } chunk1.contents.push(cell1); } } static triangulateChunks(chunkArr,cellArr,useNewChunks) { var maxDist=(.3*cellArr.length+58)/(cellArr.length+27); if(useNewChunks){Cell.setChunks(chunkArr,cellArr);} for(var ind1=0;ind1<chunkArr.length;ind1++) { var chunk1=chunkArr[ind1];chunk1.checked=true;//Prevents excessive checking of this chunk var checkArray1=chunk1.contents;//All cells in the neighborhood to be triangulated var checkArray2=chunk1.contents;//All cells that must be checked in cones for(var ind2=0;ind2<chunk1.nbors.length;ind2++) {//Puts whole neighborhood into one array if(chunk1.nbors[ind2].checked==false){checkArray1=checkArray1.concat(chunk1.nbors[ind2].contents);} checkArray2=checkArray2.concat(chunk1.nbors[ind2].contents); } for(var ind2=0;ind2<chunk1.contents.length;ind2++) {//First vertex to check var cell1=checkArray1[ind2]; for(var ind3=ind2+1;ind3<checkArray1.length-1;ind3++) {//Second vertex to check var cell2=checkArray1[ind3]; if (((cell1.x-cell2.x)*(cell1.x-cell2.x)+(cell1.y-cell2.y)*(cell1.y-cell2.y)+(cell1.z-cell2.z)*(cell1.z-cell2.z))<maxDist*maxDist) { for(var ind4=ind3+1;ind4<checkArray1.length;ind4++) {//Third vertex to check var cell3=checkArray1[ind4]; if (((cell1.x-cell3.x)*(cell1.x-cell3.x)+(cell1.y-cell3.y)*(cell1.y-cell3.y)+(cell1.z-cell3.z)*(cell1.z-cell3.z))<maxDist*maxDist &&((cell2.x-cell3.x)*(cell2.x-cell3.x)+(cell2.y-cell3.y)*(cell2.y-cell3.y)+(cell2.z-cell3.z)*(cell2.z-cell3.z))<maxDist*maxDist) { //Finds a vector perpendicular to the 3 vertices var vector1=[cell2.x-cell1.x,cell2.y-cell1.y,cell2.z-cell1.z]; var vector2=[cell3.x-cell1.x,cell3.y-cell1.y,cell3.z-cell1.z]; var cross=[vector1[1]*vector2[2]-vector1[2]*vector2[1] ,vector1[2]*vector2[0]-vector1[0]*vector2[2] ,vector1[0]*vector2[1]-vector1[1]*vector2[0]]; //Dot product to compare all other cells against. var dot1=cell1.x*cross[0]+cell1.y*cross[1]+cell1.z*cross[2]; var charge=dot1/Math.abs(dot1); //Checks to see if any points are within circumscribed cone var makeTri=true; for(var ind5=0;ind5<checkArray2.length;ind5++) //for(var ind5=0;ind5<cellArr.length;ind5++) { var cell4=checkArray2[ind5]; //var cell4=cellArr[ind5] if(cell4!=cell1&&cell4!=cell2&&cell4!=cell3) { if((charge*(cell4.x*cross[0]+cell4.y*cross[1]+cell4.z*cross[2]))>Math.abs(dot1)) {makeTri=false;break;} } } if(makeTri)//If not, makes a triangle { //For drawing triangles tris.push([cell1,cell2,cell3]); //For adding neighbors if(!cell1.nbors.includes(cell2)){cell1.nbors.push(cell2)} if(!cell1.nbors.includes(cell3)){cell1.nbors.push(cell3)} if(!cell2.nbors.includes(cell1)){cell2.nbors.push(cell1)} if(!cell2.nbors.includes(cell3)){cell2.nbors.push(cell3)} if(!cell3.nbors.includes(cell2)){cell3.nbors.push(cell2)} if(!cell3.nbors.includes(cell1)){cell3.nbors.push(cell1)} } } } } } } } for(var ind1=0;ind1<cellArr.length;ind1++)//Arranges the vertices of the cell polygons { var cell1=cellArr[ind1]; var vertSort=[]; //Angles for conversion var distH=Math.sqrt(cell1.x*cell1.x+cell1.y*cell1.y); var sinH=-cell1.y/distH; var cosH=cell1.x/distH; for(var ind2=0;ind2<cell1.nbors.length;ind2++) { var vertex=cell1.nbors[ind2]; var nx=-vertex.z*distH+(vertex.x*cosH-vertex.y*sinH)*cell1.z; var ny=vertex.x*sinH+vertex.y*cosH; vertSort.push([vertex,Math.atan2(ny,nx)]); } vertSort.sort(function(a,b){return(a[1]-b[1])}); cell1.nbors=[]; for(var ind2=0;ind2<vertSort.length;ind2++) { cell1.nbors.push(vertSort[ind2][0]); } cell1.updateVerts() } } updateVerts() { this.verts=[]; var nbor1=this.nbors[this.nbors.length-1] for(var ind=0;ind<this.nbors.length;ind++) { var nbor2=this.nbors[ind]; this.verts.push([(this.x+nbor1.x+nbor2.x)/3, (this.y+nbor1.y+nbor2.y)/3, (this.z+nbor1.z+nbor2.z)/3]) nbor1=nbor2; } } static drawAll(array,x,y,scale,hor,vert,ctx) { ctx.globalAlpha=1; var horizon=0//-Math.sin(2*Math.asin(1/Math.sqrt(array.length))); var cosH=Math.cos(hor); var sinH=Math.sin(hor); var cosV=Math.cos(vert); var sinV=Math.sin(vert); for(var ind1=0;ind1<array.length;ind1++) { var chunk1=array[ind1]; //var chunkZIndex=scale*(-(sinH*chunk1.x+cosH*chunk1.y)*sinV+cosV*chunk1.z) if(scale*(-(sinH*chunk1.x+cosH*chunk1.y)*sinV+cosV*chunk1.z)>=horizon) { for(var ind2=0;ind2<chunk1.contents.length;ind2++) { var cell1=chunk1.contents[ind2]; if(scale*(-(sinH*cell1.x+cosH*cell1.y)*sinV+cosV*cell1.z)>=horizon) { var cx=scale*(cosH*cell1.x-sinH*cell1.y); var cy=scale*((sinH*cell1.x+cosH*cell1.y)*cosV+sinV*cell1.z); ctx.beginPath(); for(var ind3=0;ind3<cell1.verts.length;ind3++) { var nx=scale*(cosH*cell1.verts[ind3][0]-sinH*cell1.verts[ind3][1]); var ny=scale*((sinH*cell1.verts[ind3][0]+cosH*cell1.verts[ind3][1])*cosV+sinV*cell1.verts[ind3][2]); nx+=((nx>cx)-(nx<cx))*.25; ny+=((ny>cy)-(ny<cy))*.25; //var nz=scale*(-(sinH*cell1.verts[ind3][0]+cosH*cell1.verts[ind3][1])*sinV+cosV*cell1.verts[ind3][2]) if(ind3==0){ctx.moveTo(x+nx,y+ny)} else{ctx.lineTo(x+nx,y+ny)} } ctx.closePath(); ctx.fillStyle=cell1.color; ctx.strokeStyle=cell1.color; ctx.fill(); //ctx.stroke(); } } } } } static terrain(array) { for(var oct=0;oct<5;oct++) { var map=Cell.vectorMap(2*(oct+1)); var valMin=0;var valMax=0; for(var ind=0;ind<array.length;ind++) { var newval=array[ind].evalGradient(map); valMin=Math.min(valMin,newval); valMax=Math.max(valMax,newval); } for(var ind=0;ind<array.length;ind++) { var oldval=array[ind].gradients[array[ind].gradients.length-1]; array[ind].gradients[array[ind].gradients.length-1]=(oldval-valMin)/(valMax-valMin); } } for(var ind=0;ind<array.length;ind++) { var point=array[ind]; var height=(point.gradients[0] +Math.abs(2*point.gradients[1]-1)/2 +Math.abs(2*point.gradients[2]-1)/3 +Math.abs(2*point.gradients[3]-1)/4 +Math.abs(2*point.gradients[4]-1)/5)*60/137 point.color=`hsl(0,0%,${Math.floor(height*100)}%)`; } } } class Star { constructor() { this.z=2*Math.random()-1; var theta=2*Math.PI*Math.random(); this.x=Math.sqrt(1-this.z*this.z)*Math.cos(theta); this.y=Math.sqrt(1-this.z*this.z)*Math.sin(theta); var hue=Math.floor(64*Math.random());//Red stars if(Math.random()>.75){hue=180+80*Math.random()}//Blue stars this.color=`hsl(${hue}, ${Math.floor(100*Math.random())}%, ${75+25*Math.random()}%)`; this.radius=1+Math.random()*4; this.rays=Math.floor(6*Math.random()); this.glare=1+2*Math.random(); } draw(x,y,scale,hor,vert,ctx) { var x1=x+scale*(this.x*Math.cos(hor)-this.y*Math.sin(hor)) var y1=y+scale*((this.x*Math.sin(hor)+this.y*Math.cos(hor))*Math.cos(vert)-this.z*Math.sin(vert)); var z1=scale*((this.x*Math.sin(hor)+this.y*Math.cos(hor))*Math.sin(vert)+this.z*Math.cos(vert)); if(z1>0&&x1>0&&x1<canvas.width&&y1>0&&y1<canvas.height) { ctx.lineCap='round'; ctx.globalAlpha=.2 for(var layer=0;layer<5;layer++) { ctx.beginPath(); var scale2=scale*(1+layer/4)/1000; ctx.arc(x1,y1,this.radius*scale2,0,Math.PI*2); for(var ray=0;ray<this.rays;ray++) { for(var angle=0;angle<2*ray;angle++) { ctx.moveTo(x1+(1-ray/8)*scale2*this.radius*this.glare*Math.cos(Math.PI*angle/ray/2), y1+(1-ray/8)*scale2*this.radius*this.glare*Math.sin(Math.PI*angle/ray/2)); ctx.lineTo(x1-(1-ray/8)*scale2*this.radius*this.glare*Math.cos(Math.PI*angle/ray/2), y1-(1-ray/8)*scale2*this.radius*this.glare*Math.sin(Math.PI*angle/ray/2)); } } ctx.fillStyle=this.color; ctx.strokeStyle=this.color; ctx.lineWidth=scale2; ctx.stroke();ctx.fill(); } } } } var stars=[]; for(var i=0;i<1000;i++){stars.push(new Star)}; var t=0; var vert=Math.PI/2; var hor=0; //function setup(){ //Initializes canvas var canvas=document.createElement("canvas"); document.body.appendChild(canvas); canvas.width=window.innerWidth; canvas.height=window.innerHeight; canvas.style="position:absolute;left:0px;top:0px;background-color:black;" var ctx=canvas.getContext('2d'); var scale=Math.sqrt(canvas.width*canvas.width/4+canvas.height*canvas.height/4); document.body.onresize=function(){canvas.width=window.innerWidth; canvas.height=window.innerHeight; scale=Math.sqrt(canvas.width*canvas.width/4+canvas.height*canvas.height/4);} //Initializes and calculates vertices console.time('gen') console.time('ico'); var chunks=Cell.icosahedron(12,function(obj){});console.timeEnd('ico'); console.time('dist'); //var cells=Cell.distribute(chunks,8,Cell.setupRPS);console.log(cells.length) var cells=Cell.distribute(chunks,8,Cell.setupTerra);console.log(cells.length) console.timeEnd('dist'); console.time('tri'); Cell.triangulateChunks(chunks,cells,false);console.timeEnd('tri') console.time('terra'); Cell.terrain(cells);console.timeEnd('terra') console.timeEnd('gen');//console.log(cells) //Cell.setChunks(chunks,cells) var int=setInterval(function(){ hor+=.01; //vert+=.001; ctx.clearRect(0,0,canvas.width,canvas.height); //Cell.automaton(cells); for(var ind=0;ind<stars.length;ind++){stars[ind].draw(canvas.width/2,canvas.height/2,1.5*scale,hor,vert,ctx)} Cell.drawAll(chunks,canvas.width/2,canvas.height/2,canvas.height*3/8,hor,vert,ctx); },100) document.body.onresize=function(){canvas.width=window.innerWidth;canvas.height=window.innerHeight} //}

!