Based on Cubic space division by Escher: wikiart.org/en/m-c-escher/cubic-space-division.
#polygons #3D #Escher
Log in to post a comment.
// Cubic space division #1. Created by Reinder Nijhoff 2018 // @reindernijhoff // // https://turtletoy.net/turtle/4487884a96 // const drawOutlines = false; const hatchDensity = .0025; const hatchImperfection = .1; const fogExp = 1.3; Canvas.setpenopacity(.5); const turtle = new Turtle(); let viewProjectionMatrix; const polygonList = []; const lw = .25; const ll = 5; const dim = 12; function walk(i) { if (i == 0) { setupCamera(); } for (let j=0; j<dim; j++) { const x = 18.5 -(i%dim) * (2*ll+1); const z = 50 -((i/dim)|0) * (2*ll+1); const y = 32 -(j) * (2*ll+1); drawCube(turtle, x,y,z, 1,1,1); drawCube(turtle, x-ll,y,z, ll,lw,lw); drawCube(turtle, x,y,z-ll, lw,lw,ll); drawCube(turtle, x,y-ll,z, lw,ll,lw); } return i < dim*dim; } function drawCube(turtle, x, y, z, w, h, d) { const vl = [ [x-w,y-h,z+d,1], [x+w,y-h,z+d,1], [x+w,y+h,z+d,1], [x-w,y+h,z+d,1], [x-w,y+h,z-d,1], [x+w,y+h,z-d,1], [x+w,y-h,z-d,1], ]; const il = [0,1,2,3, 1,2,5,6, 5,2,3,4]; for (let i=0; i<3; i++) { let vis = true; let dist = 0; const p = new Polygon(); for (let j=0; j<4; j++) { const v = transform4(vl[il[j+i*4]], viewProjectionMatrix); p.cp.push([v[0]/v[3]*50, -v[1]/v[3]*50]); if (v[3] < 0) vis = false; dist = Math.max(dist, v[3]); } if (vis) { vis = false; for (let j=0; j<p.cp.length; j++) { if (Math.abs(p.cp[j][0]) < 100 && Math.abs(p.cp[j][1]) < 100) { vis = true; } } if (vis) { if (drawOutlines) { p.addOutline(i); } p.addHatching( .5, hatchDensity*(1+i/3)*Math.pow(dist, fogExp)); drawPolygon(turtle, p); } } } } function drawPolygon(turtle, p) { for (let j=0; j<polygonList.length; j++) { if(!p.boolean(polygonList[j])) { return; } } p.draw(turtle, hatchImperfection); polygonList.push(p); } function setupCamera() { viewMatrix = lookAt4m([15,30,50], [-10,0,0], [0,1,0]); projectionMatrix = perspective4m(0.3, 1, .1); viewProjectionMatrix = multiply4m(projectionMatrix, viewMatrix); } // polygon functions function LineSegment(p1, p2) { this.p1 = p1; this.p2 = p2; } function Polygon() { this.cp = []; // clip path: array of [x,y] pairs this.dp = []; // 2d line to draw: array of linesegments } Polygon.prototype.addOutline = function(s=0) { for (let i=s, l=this.cp.length; i<l; i++) { this.dp.push(new LineSegment(this.cp[i], this.cp[(i+1)%l])); } } Polygon.prototype.createPoly = function(x,y,c,r,a) { this.cp = []; for (let i=0; i<c; i++) { this.cp.push( [x + Math.sin(i*Math.PI*2/c+a) * r, y + Math.cos(i*Math.PI*2/c+a) * r] ); } } Polygon.prototype.addHatching = function(a,d) { // todo, create a tight bounding polygon, for now fill screen const tp = new Polygon(); tp.createPoly(0,0,4,200,Math.PI*.5); const dx = Math.sin(a)*d, dy = Math.cos(a)*d; const cx = Math.sin(a)*200, cy = Math.cos(a)*200; for (let i = .5; i<150/d; i++) { tp.dp.push(new LineSegment([dx*i+cy,dy*i-cx], [dx*i-cy,dy*i+cx])); tp.dp.push(new LineSegment([-dx*i+cy,-dy*i-cx], [-dx*i-cy,-dy*i+cx])); } tp.boolean(this, false); this.dp = this.dp.concat(tp.dp); } Polygon.prototype.draw = function(t, inp=0) { if (this.dp.length ==0) { return; } for (let i=0, l=this.dp.length; i<l; i++) { const d = this.dp[i]; if (!equal2(d.p1, t.pos())) { t.penup(); t.goto([d.p1[0]+inp*(Math.random()-.5), d.p1[1]+inp*(Math.random()-.5)]); t.pendown(); } t.goto([d.p2[0]+inp*(Math.random()-.5), d.p2[1]+inp*(Math.random()-.5)]); } } Polygon.prototype.inside = function(p) { // find number of intersections from p to far away - if even you're outside const p1 = [0, -1000]; let int = 0; for (let i=0, l=this.cp.length; i<l; i++) { if (segment_intersect2(p, p1, this.cp[i], this.cp[(i+1)%l])) { int ++; } } return int & 1; } Polygon.prototype.boolean = function(p, diff = true) { // very naive polygon diff algorithm - made this up myself const ndp = []; for (let i=0, l=this.dp.length; i<l; i++) { const ls = this.dp[i]; // find all intersections with clip path const int = []; for (let j=0, cl=p.cp.length; j<cl; j++) { const pint = segment_intersect2(ls.p1,ls.p2,p.cp[j],p.cp[(j+1)%cl]); if (pint) { int.push(pint); } } if (int.length == 0) { // 0 intersections, inside or outside? if (diff != p.inside(ls.p1)) { ndp.push(ls); } } else { int.push(ls.p1); int.push(ls.p2); // order intersection points on line ls.p1 to ls.p2 const cmp = sub2(ls.p2,ls.p1); int.sort((a,b) => dot2(sub2(a,ls.p1),cmp)-dot2(sub2(b,ls.p1),cmp)); for (let j=0; j<int.length-1; j++) { if (!equal2(int[j], int[j+1]) && diff != p.inside(scale2(add2(int[j],int[j+1]),.5))) { ndp.push(new LineSegment(int[j], int[j+1])); } } } } this.dp = ndp; return this.dp.length > 0; } // vec2 functions const equal2=(a,b)=>.001>dist_sqr2(a,b); const scale2=(a,b)=>[a[0]*b,a[1]*b]; const add2=(a,b)=>[a[0]+b[0],a[1]+b[1]]; const sub2=(a,b)=>[a[0]-b[0],a[1]-b[1]]; const dot2=(a,b)=>a[0]*b[0]+a[1]*b[1]; const dist_sqr2=(a,b)=>(a[0]-b[0])*(a[0]-b[0])+(a[1]-b[1])*(a[1]-b[1]); const segment_intersect2=(a,b,d,c)=>{ const e=(c[1]-d[1])*(b[0]-a[0])-(c[0]-d[0])*(b[1]-a[1]); if(0==e)return false; c=((c[0]-d[0])*(a[1]-d[1])-(c[1]-d[1])*(a[0]-d[0]))/e; d=((b[0]-a[0])*(a[1]-d[1])-(b[1]-a[1])*(a[0]-d[0]))/e; return 0<=c&&1>=c&&0<=d&&1>=d?[a[0]+c*(b[0]-a[0]),a[1]+c*(b[1]-a[1])]:false; } // vec3 functions const scale3=(a,b)=>[a[0]*b,a[1]*b,a[2]*b]; const len3=(a)=>Math.sqrt(dot3(a,a)); const normalize3=(a)=>scale3(a,1/len3(a)); const add3=(a,b)=>[a[0]+b[0],a[1]+b[1],a[2]+b[2]]; const sub3=(a,b)=>[a[0]-b[0],a[1]-b[1],a[2]-b[2]]; const dot3=(a,b)=>a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; const cross3=(a,b)=>[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]] // vec4 functions const transform4=(a,b)=>{ const d=new Float32Array(4); for(let c=0;4>c;c++)d[c]=b[c]*a[0]+b[c+4]*a[1]+b[c+8]*a[2]+b[c+12]*a[3]; return d; } // mat4 functions const lookAt4m=(a,b,d)=>{ // pos, lookAt, up const c=new Float32Array(16); b=normalize3(sub3(a,b)); d=normalize3(cross3(d,b)); const e=normalize3(cross3(b,d)); c[0]=d[0];c[1]=e[0];c[2]=b[0];c[3]=0; c[4]=d[1];c[5]=e[1];c[6]=b[1];c[7]=0; c[8]=d[2];c[9]=e[2];c[10]=b[2];c[11]=0; c[12]=-(d[0]*a[0]+d[1]*a[1]+d[2]*a[2]); c[13]=-(e[0]*a[0]+e[1]*a[1]+e[2]*a[2]); c[14]=-(b[0]*a[0]+b[1]*a[1]+b[2]*a[2]); c[15]=1; return c; } const multiply4m=(a,b)=>{ const d=new Float32Array(16); for(let c=0;16>c;c+=4) for(let e=0;4>e;e++) d[c+e]=b[c+0]*a[0+e]+b[c+1]*a[4+e]+b[c+2]*a[8+e]+b[c+3]*a[12+e]; return d; } const perspective4m=(a,b,d)=>{ // fovy, aspect. near const c=(new Float32Array(16)).fill(0,0); c[5]=1/Math.tan(a/2); c[0]=c[5]/b; c[10]=c[11]=-1; c[14]=-2*d; return c; }