// You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(2); const PATH_PRECISION = .5; const totalPathPoints = 180; // min=5, max=5000, step=1 const totalPathSpiral1 = 0.69; // min=0.01, max=10, step=0.01 const totalPathSpiral2 = 0.13; // min=0.001, max=2, step=0.001 let houseWidth = 15; // min=5, max=40, step=1 houseWidth /= PATH_PRECISION; let houseDistance = 30; // min=10 max=100, step=1 houseDistance /= PATH_PRECISION; let houseHeight = 14; // min=1 max=100, step=1 let houseDepth = 10; // min=1 max=100, step=1 houseDepth /= PATH_PRECISION; const roof = 25; // min=10 max=100, step=0.5 // 3d depth const z = 0.94; // min=0.5 max=1, step=0.01 // Global code will be evaluated once. const turtle = new Turtle(); turtle.pendown(); let path; let objectId = (Math.random() * 777) | 0; // The walk function will be called until it returns false. function walk(i) { if (i === 0) { setup(); } // line of the path turtle.jump(path[i][0], path[i][1]); turtle.goto(path[i+1][0], path[i+1][1]); // houses if (i > 4 && i % houseDistance === 0 && i + houseWidth+houseDepth < path.length - 1) { if (objectId % 4 < 2) drawHouse(i, i + houseWidth); else if(objectId % 4 == 2) { if(Math.random()>0.1)drawTree(i+ 5, houseHeight * 0.5); if(Math.random()>0.1)drawTree(i+ 5 + houseWidth / 2, houseHeight*0.75); if(Math.random()>0.1)drawTree(i+ 5 + houseWidth, houseHeight * 0.5); } else drawFlat(i, i + houseWidth); objectId ++; } return i < path.length - 2; } function drawTree(x, treeHeight) { moveTo(path, x - 1); lineTo(path, x - 1, treeHeight); lineTo(path, x - 1 , treeHeight + 2); lineTo(path, x - 1 - randomRange(6,7), treeHeight + 4); lineTo(path, x - 1 - randomRange(5,7), treeHeight + 10); lineTo(path, x - 1 - randomRange(3,4), treeHeight + 14); lineTo(path, x , treeHeight + 18); lineTo(path, x + 1 + randomRange(3,4), treeHeight + 14); lineTo(path, x + 1 + randomRange(5,7), treeHeight + 10); lineTo(path, x + 1 + randomRange(6,7), treeHeight + 4); lineTo(path, x + 1, treeHeight); lineTo(path, x + 1); } function drawHouse(x1,x2) { var hasLine = Math.random()>.5; var doubleLine = Math.random()>.5; var windowHeight = 3; // walls moveTo(path, x1); lineTo(path, x1, houseHeight); var roofX = ((x1 + x2) * 0.5) | 0 lineTo(path, roofX, roof); lineTo(path, x2, houseHeight); lineTo(path, x2); // 3d depth line(path, roofX, roof* z, roofX + houseDepth, roof ); lineTo(path, x2 + houseDepth, houseHeight * z); line(path, x2, houseHeight * z, x2 + houseDepth, houseHeight); moveTo(path, x2 + houseDepth, houseHeight * z); lineTo(path, x2 + houseDepth); // wall/roof line if (hasLine) line(path, x1, houseHeight, x2, houseHeight); if (hasLine && doubleLine) { line(path, x1, houseHeight-1, x2, houseHeight-1); // double line line(path, x2, houseHeight * z-1, x2 + houseDepth, houseHeight-1); } // door var doorWidth = 3; var doorX = Math.random() > 0.5 ? x1 + 2 : x2 - 2 - doorWidth; moveTo(path, doorX); lineTo(path, doorX, 5); lineTo(path, doorX + doorWidth, 5); lineTo(path, doorX + doorWidth); // windows for(let ii=0;ii<=2;ii++) { var x = ii * 3; if (Math.random()> 0.5 && 8+windowHeight < houseHeight) { moveTo(path, x+x1 + 2, 8); lineTo(path, x+x1 + 2, 8+windowHeight); lineTo(path, x+x1 + 4, 8+windowHeight); lineTo(path, x+x1 + 4, 8); lineTo(path, x+x1 + 2, 8); } } } function drawFlat(x1,x2) { var doubleLine = Math.random()>.5; var windowHeight = 3; const flatHeight = roof; // walls moveTo(path, x1); lineTo(path, x1, flatHeight); moveTo(path, x2, flatHeight); lineTo(path, x2); line(path, x2, flatHeight * z, x2 + houseDepth, flatHeight); moveTo(path, x2 + houseDepth, flatHeight * z); lineTo(path, x2 + houseDepth); // wall/roof line line(path, x1, flatHeight, x2, flatHeight); if (doubleLine) { line(path, x1, flatHeight-1, x2, flatHeight-1); // double line line(path, x2, flatHeight * z-1, x2 + houseDepth, flatHeight-1); } // door var doorWidth = 3; var doorX = Math.random() > 0.5 ? x1 + 2 : x2 - 2 - doorWidth; moveTo(path, doorX); lineTo(path, doorX, 5); lineTo(path, doorX + doorWidth, 5); lineTo(path, doorX + doorWidth); // windows var windowWidth = 4; for(let ii=0;ii<=14;ii++) { var x = ii * windowWidth; if (x>1&&x +windowWidth +1 < houseWidth) { for(let jj=0;jj<14;jj++) { var h = 8 + jj * 4; if (Math.random() > 0.6 && h+windowHeight+1 < roof) { moveTo(path, x+x1, h); lineTo(path, x+x1, h+windowHeight); lineTo(path, x+x1+windowWidth, h+windowHeight); lineTo(path, x+x1+windowWidth, h); lineTo(path, x+x1, h); } } } } } function getPos(path, x, y = 0) { var idx = x|0; var p1 = path[idx+1]; var p2 = path[idx]; var a = angle(p2, p1) - Math.PI*0.5; // TODO: fix this interpolation somehow.. var t = x - idx; // 10.34 - 10 = 0.34 var xx = lerp(p2[0], p1[0], t); var yy = lerp(p2[1], p1[1], t); return [xx + Math.cos(a) * y, yy + Math.sin(a) * y]; } function moveTo(path, x, y = 0) { var pos = getPos(path, x, y); turtle.jump(pos[0], pos[1]); } function lineTo(path, x, y = 0) { var pos = getPos(path, x, y); turtle.goto(pos[0], pos[1]); } function line(path, x1, y1, x2, y2, precision = PATH_PRECISION/2) { var xa = Math.min(x1,x2); var xb = Math.max(x1,x2); for(let x=xa;x<=xb; x+=precision) { var t=((xb-x)/(xb-xa)); if (x==x1) moveTo(path, x, lerp(y1,y2,t)); else lineTo(path, x, lerp(y1,y2,t)); } //lineTo(path, xx, y2); } function setup(){ path = createPath(); path = normalizePath(path, PATH_PRECISION); console.log(path); } // 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 dist=(a,b)=>Math.sqrt(dist_sqr2(a,b)); const angle=(a,b)=>Math.atan2(b[1]-a[1], b[0]-a[0]); const center=(a,b)=>[(a[0]+b[0])/2,(a[1]+b[1])/2]; const lerp=(a,b,t)=>a+(b-a)*t; const clone=(a)=>[a[0],a[1]]; const clamp01=(t)=>Math.min(1.0,Math.max(0.0,t)); const randomRange=(a,b)=>a+Math.random()*(b-a) const createPath = ()=> { const path = []; const total = totalPathPoints; for (let i = 0; i < total; i++) { let r = i * totalPathSpiral1; path.push([r * Math.cos(i*totalPathSpiral2), r * Math.sin(i*totalPathSpiral2)]); } return path; } const normalizePath = (path, minDist) => { var newPath = []; let d = 0.0 let prev = path[0]; newPath.push(clone(prev)); for (let i = 1; i < path.length; i++) { let curr = clone(path[i]); d = dist(prev, curr); var a = angle(prev, curr); while (d > minDist) { prev[0] += Math.cos(a) * minDist; prev[1] += Math.sin(a) * minDist; newPath.push(clone(prev)); d -= minDist; } } return newPath; }