Based on Cubic space division by Escher: wikiart.org/en/m-c-escher/cubic-space-division.
Cubic space division 1: Cubic space division #1
#polygons #3D #Escher
Log in to post a comment.
// Forked from "Cubic space division #2" by reinder // https://turtletoy.net/turtle/25b7bc4d43 // Cubic space division #2. Created by Reinder Nijhoff 2018 // @reindernijhoff // // https://turtletoy.net/turtle/25b7bc4d43 // const drawImperfection =0; // try 1` Canvas.setpenopacity(.75); const turtle = new Turtle(); let viewProjectionMatrix; const polygonList = []; const rods = 0.25; // min=0.1 max=.5 step=0.1 const scale= 4; //min=1 max=7 step=1 const itterations = 14; //min=2 max=15 step=1 let minLen = 0; //min=0 max=5 step=0.5 // minimum length of line to plot let len = 0; // used to measure the length of the line to draw let x1; let x2; let y1; let y2; function walk(i) { if (i == 0) { setupCamera(); } for (let j=0; j<itterations; j++) { const x = 18.5 -(i%itterations) * (2*scale+1); const z = 50 -((i/itterations)|0) * (2*scale+1); const y = 32 -(j) * (2*scale+1); drawCube(turtle, x,y,z, 1,1,1); drawCube(turtle, x-scale,y,z, scale,rods,rods); drawCube(turtle, x,y,z-scale, rods,rods,scale); drawCube(turtle, x,y-scale,z, rods,scale,rods); } return i < itterations*itterations; } 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) { p.addOutline(i); drawPolygon(turtle, p); } } } } function drawPolygon(turtle, p) { for (let j=0; j<polygonList.length; j++) { if(!p.boolean(polygonList[j])) { return; } } p.draw(turtle, drawImperfection); 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.draw = function(t, inp=0) { if (this.dp.length ==0) { return; } // draw the lines for (let i=0, l=this.dp.length; i<l; i++) { const d = this.dp[i]; // only draw lines if over a given length // https://www.wikihow.com/Use-Distance-Formula-to-Find-the-Length-of-a-Line // calcuate the distance between the 2 points x1 = [d.p1[0]+inp*(Math.random()-.5)]; x2 = [d.p2[0]+inp*(Math.random()-.5)]; y1 = [d.p1[1]+inp*(Math.random()-.5)]; y2 = [d.p2[1]+inp*(Math.random()-.5)]; len = Math.sqrt(Math.pow((x2 - x1),2) + Math.pow((y2-y1),2)); if (len > minLen) { 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; }