Houses on path 🏠🌳

Drawing houses and some trees on path. Made a custom lineto/moveto functions that take path angle in account.

Inspired by my very own drawing instagram.com/p/B1D7RpiI4p-/

Log in to post a comment.

// 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;
}