Hexagon crawler 🧊

Start at the center, place another hexagon on random edge (if there isnt already one placed there) and recurse.

#hexagons

Log in to post a comment.

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);

const seed = 23 // min=1, max=200, step=1
const turtle = new Turtle();
const polys = new Polygons();

const totalIterations = 500; // min=10, max=10000, step=1
const totalSpawners = 1; // min=1, max=50, step=1
const totalSizes = 3; // min=1, max=10, step=1
const startSize = 1.25; // min=0.05, max=10, step = 0.05
const useHatching = 1; // min=0, max=1, step=1 (No, Yes)
const crawlType = 0; // min=0, max=1, step=1 (Random, Less random)
const debug = 0 // min=0, max=1, step=1 (No, Yes)

let size = startSize;
let hexagonSizes = Array.from(Array(totalSizes), (_, idx) => createHexagon(0, 0).map(p => scl2(p, size), size += size));

let crawlPositions, crawlPts;

let crawlDir = 1;
let crawlId = 0;
let iterations = 0;

function walk(i) {
    if (i === 0) {
        iterations = 0;
        crawlPositions = Array.from(Array(totalSpawners), _ => [0, 0]);
        crawlPts = [];
        rseed = seed;
    }
    
    let iii = 0;
    for(let crawlPos of crawlPositions) {
        const hexagon = hexagonSizes[(rand() * hexagonSizes.length) | 0];
        let hexagonEdgeIdx = 0;
        if (crawlType == 0) {
            hexagonEdgeIdx = (rand() * hexagon.length) | 0;
        } else if (crawlType == 1) {
            hexagonEdgeIdx = modulo(crawlId += crawlDir, hexagon.length);
            if (crawlId % 36 == 0) crawlDir *= -1;
        }
        const pos = hexagon[hexagonEdgeIdx];
        crawlPos = add2(crawlPos, pos);
        crawlPositions[iii] = crawlPos;
        
         // not allowed to put another hexagon on a position we already used
        if (!crawlPts.find(p => eq(p, crawlPos))) {
            crawlPts.push(crawlPos); // store pos
            
            // split hexagon in 3 shapes to make a cube
            hexagon.forEach((p,idx,a) => {
                if (idx % 2 ==0) {
                    let shape = [a[idx],a[(idx+1)%a.length], a[(idx+2)%a.length], getShapeMiddle(a)];
                    shape = shape.map(p => scl2(add2(p, crawlPos), 1));
                    
                    if (!debug) {
                        drawPolygon(shape, useHatching ? [0, -1, 1,][idx % 3] : 0);
                    } else {
                        drawPoints(shape, turtle);
                    }
                }
            });
            iterations ++;
        }
        
        iii++;
    }
    return iterations < totalIterations;
}

function modulo(i,  n) {
    // positive modulo
    return (n + (i % n)) % n;
}

const addHatching = true;
function drawPolygon(cp, h = 0.5, debug = false) {
    const poly = polys.create();
    poly.addPoints(...cp);
    poly.addOutline();
    if (h && addHatching) {
        poly.addHatching(h * Math.PI/4, 0.75 - 0.25 * h);
    }
    polys.draw(turtle, poly, !debug);
}

function getShapeMiddle(shape) {
    return scl2(shape.reduce((curr,p) => curr = add2(curr, p), [0,0]), 1/shape.length);
}

function createHexagon(x,y) {
    const h = .75, w = 3/8 * (h/(Math.sqrt(3)/4)), center = [x*2*w + (y%2)*w, y*3/2*h];
    return [[0,-h],[-w,-h/2],[-w,h/2],[0,h],[w,h/2],[w,-h/2]].map(p => add2(center, p));
}

function drawPoints(points, turtle) {
    if (points.length > 0) turtle.jump(points[points.length-1]);
    points.forEach(point => turtle.goto(point));
}
// vec2 functions
function scl2(a,b)   { return [a[0]*b, a[1]*b]; }
function add2(a,b)   { return [a[0]+b[0], a[1]+b[1]]; }
function sub2(a,b)   { return [a[0]-b[0], a[1]-b[1]]; }
function dot2(a,b)   { return a[0]*b[0] + a[1]*b[1]; }
function cross2(a,b) { return a[0]*b[1] - a[1]*b[0]; }
function len2(a)     { return Math.sqrt(a[0]**2 + a[1]**2); }
function dist2(a, b) { return len2(sub2(a,b)); }
function nrm2(a)     { return scl2(a, 1/len2(a)); }
function lrp2(a,b,f) { return [a[0]*f+b[0]*(1-f), a[1]*f+b[1]*(1-f)]; }
function lrp(a,b,f) { return a*f + b*(1-f); }
function eq(a,b) { return a[0] === b[0] && a[1] === b[1] }
function neq(a,b) { return !eq(a,b) }

// pseudo random methods
function hash(p) {
    p += seed;
    p = 1103515245 * (((p) >> 1) ^ (p));
    p = 1103515245 * (p ^ (p>>3));
    p = p ^ (p >> 16);
    return p / 1103515245 % 1;	
}

let rseed = seed;
function rand() {
    let r = 1103515245 * (((rseed) >> 1) ^ (rseed++));
    r = 1103515245 * (r ^ (r>>3));
    r = r ^ (r >> 16);
    return r / 1103515245 % 1;	
}


////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/a5befa1f8d
////////////////////////////////////////////////////////////////
function Polygons(){let t=[];const s=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,h=1e5,i=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),h=Math.min(h,t[1]),i=Math.max(i,t[1])}),this.aabb=[(s+e)/2,(h+i)/2,(e-s)/2,(i-h)/2]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(i=0){for(let t=i,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t,e){const h=new s;h.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const i=Math.sin(t)*e,n=Math.cos(t)*e,a=200*Math.sin(t),p=200*Math.cos(t);for(let t=.5;t<150/e;t++)h.dp.push([i*t+p,n*t-a],[i*t-p,n*t+a]),h.dp.push([-i*t+p,-n*t-a],[-i*t-p,-n*t+a]);h.boolean(this,!1),this.dp=[...this.dp,...h.dp]}inside(t){let s=0;for(let e=0,h=this.cp.length;e<h;e++)this.segment_intersect(t,[.13,-1e3],this.cp[e],this.cp[(e+1)%h])&&s++;return 1&s}boolean(t,s=!0){if(s&&Math.abs(this.aabb[0]-t.aabb[0])-(t.aabb[2]+this.aabb[2])>=0&&Math.abs(this.aabb[1]-t.aabb[1])-(t.aabb[3]+this.aabb[3])>=0)return this.dp.length>0;const e=[];for(let h=0,i=this.dp.length;h<i;h+=2){const i=this.dp[h],n=this.dp[h+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const h=this.segment_intersect(i,n,t.cp[s],t.cp[(s+1)%e]);!1!==h&&a.push(h)}if(0===a.length)s===!t.inside(i)&&e.push(i,n);else{a.push(i,n);const h=n[0]-i[0],p=n[1]-i[1];a.sort((t,s)=>(t[0]-i[0])*h+(t[1]-i[1])*p-(s[0]-i[0])*h-(s[1]-i[1])*p);for(let h=0;h<a.length-1;h++)(a[h][0]-a[h+1][0])**2+(a[h][1]-a[h+1][1])**2>=.001&&s===!t.inside([(a[h][0]+a[h+1][0])/2,(a[h][1]+a[h+1][1])/2])&&e.push(a[h],a[h+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,h){const i=(h[1]-e[1])*(s[0]-t[0])-(h[0]-e[0])*(s[1]-t[1]);if(0===i)return!1;const n=((h[0]-e[0])*(t[1]-e[1])-(h[1]-e[1])*(t[0]-e[0]))/i,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/i;return n>=0&&n<=1&&a>=0&&a<=1&&[t[0]+n*(s[0]-t[0]),t[1]+n*(s[1]-t[1])]}};return{list:()=>t,create:()=>new s,draw:(s,e,h=!0)=>{for(let s=0;s<t.length&&e.boolean(t[s]);s++);e.draw(s),h&&t.push(e)}}}