Happy Easter 2024

One of the 'painted' eggs is upside down

Combining Hatchery Hatchakid矇e and Eggs

Log in to post a comment.

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

// Global code will be evaluated once.
init();
loadHatcheryNamespace();
const turtle = new Turtle();
const polygons = new Polygons();
const grid = new SquareGrid(gridSize, 5);

class Hatchery {

    static getRandom(location) {
        return [Math.random()].map(rnd => [
            (location) => new HerringboneHatching(1, [[7,0], [0,3.5]]),
            (location) => new ChevronHatching(0, [[9,0],[2,3]]),
            (location) => new CircularHatching(2, location),
            (location) => new HerringboneHatching(1, [[7,0], [2,3.5]], 3),
            (location) => new SpiralHatching(3, location, 1, 1, -1.4),
            (location) => new BrickHatching(.2, 5, 3),
            (location) => new TreadPlateHatching(7, .5, .7, 3),
            (location) => new SineHatching(10, 2, 1, 4),
            (location) => new PolkaDotHatching(Math.PI / 5, 6, 1.5),
            (location) => new HoneyCombHatching(1.2, 10),
            (location) => new TriangleHatching(1.5, 4),
            (location) => new QBertHatching(1.2, 8),
            (location) => new ZigZagHatching(7, 3, 2.55, 4),
            (location) => new WeaveHatching(8, .5, 1, .3, 3),
            (location) => new RadialHatching(20, location),
            (location) => new SineRadialHatching(15, location),
            (location) => new WeaveHatching(7, .7, 1, .5),
            (location) => new WeaveHatching(6, .4, .4, .4, 2, 0, false),
            (location) => new WeaveHatching(6, .8, .3, 1.4),
            (location) => new LineHatching(1 + Math.PI / 2, 2),
            (location) => new ZigZagRadialHatching(15, location),
            (location) => new BlockRadialHatching(15, location),
        ].filter((e,i,a)=>i==(rnd*a.length|0))[0](location)).pop();
    }
}

// The walk function will be called until it returns false.
const iterator = grid.iterator();
const chosen = Math.random() * grid.length | 0;
const innerEgg = eggPoints(100).map(pt => V.add(pt, [0, 50]));
const outerEgg = eggPoints(160).map(pt => V.add(pt, [0, 80]));

let miniCount = 0;
function walk(i) {
    if(miniCount > 0) {
        const rpt = [Math.random(), Math.random()].map(l => (l - .5) * 200);
        if(!Intersection.inside(outerEgg, rpt) && miniCount < 1000) return true;
        const p = polygons.create();
        const s = Intersection.inside(innerEgg, rpt)? 40: !Intersection.inside(outerEgg, rpt)? 7: 25;
        p.addPoints(...eggPoints(s).map(pt => V.add([0, s/2], V.add(pt, rpt))))
        p.addOutline();
        polygons.draw(turtle, p);

        return miniCount++ < 5000;
    }
    
    const iteration = iterator.next();
    const cell = iteration.value;

    const p = polygons.create();
    p.addPoints(...eggPoints(cell.size).map(pt => V.add([0, cell.size/2 * (i == chosen?-1:1)], V.add(cell.center, V.trans(V.rot2d(i == chosen? Math.PI: 0), pt)))));
    p.addOutline();
    p.addHatching(Hatchery.getRandom(V.add(cell.center, [0, -cell.size/1.8])));
    polygons.draw(turtle, p);
    
    if(iteration.done) miniCount++;
    
    return true;
}

function eggPoints(height) {
    const width = 2 * height / (4 - Math.SQRT2);
    
    const innerRadius = width / 2;
    const upperRadius = height - width;

    const O = [0, -innerRadius]; //innerCenter
    const Q = [0, -width]; //upperCenter
    const A = [-innerRadius, -innerRadius]; //most left pt
    const B = [innerRadius, -innerRadius]; //most right pt
    
    return [
        ...PT.arc(innerRadius, Math.PI/2, Math.PI/2).map(pt => V.add(pt, O)),
        ...PT.arc(2*innerRadius, Math.PI/4, Math.PI).map(pt => V.add(pt, B)),
        ...PT.arc(upperRadius, Math.PI/2, 5*Math.PI/4).map(pt => V.add(pt, Q)),
        ...PT.arc(2*innerRadius, Math.PI/4, -Math.PI/4).map(pt => V.add(pt, A)),
        ...PT.arc(innerRadius, Math.PI/2, 0).map(pt => V.add(pt, O)),
    ];
}

////////////////////////////////////////////////////////////////
// Square Grid utility code - Created by Jurgen Westerhof 2024
// Modified: https://turtletoy.net/turtle/6c24a2a461
////////////////////////////////////////////////////////////////
function SquareGrid(grid, padding=2, margin = 5, populateFn = (column, row) => false, canvasSize = 200) {
    class SquareGrid {
        //cells[column][row] is a 2d array holding the values of the grid
        constructor(grid, padding = 2, margin = 5, populateFn = (column, row) => false, canvasSize = 200) {this.gridSize = grid;this.padding = padding;this.margin = margin;this.cells = Array.from({length: this.gridSize}).map((e, column) => Array.from({length: this.gridSize}).map((e, row) => populateFn(column, row)));this.canvasSize = canvasSize;this.resetCellSize();}
        resetCellSize() { this.cellSize = ((this.canvasSize - this.margin - this.margin - ((this.gridSize - 1) * this.padding)) / this.gridSize);}
        getColumnRow(i) { return [i % this.gridSize, i / this.gridSize | 0]; }
        getCellCenter(col, row) { return [col, row].map(v => this.margin - 100 + (v * this.padding) + ((v + .5) * this.cellSize)); }
        getCell(col, row) { return { center: this.getCellCenter(col, row), colrow: [col, row], iteration: col + row * this.gridSize, size: this.cellSize, value: this.cells[col][row] }}
        get length() { return this.gridSize**2; }
        *iterator() { let ptr = 0; while(ptr < this.length - 1) { yield this.getCell(...this.getColumnRow(ptr++)); } return this.getCell(...this.getColumnRow(ptr++)); }
        getValue(col, row) { return this.cells[col][row]; }
        setValue(col, row, value) { this.cells[col][row] = value; }
        isValid(col, row) { return 0 <= col && col < this.gridSize && 0 <= row && row < this.gridSize; }
    }
    return new SquareGrid(grid, padding, margin, populateFn, canvasSize);
}

function init() {
    ///////////////////////////////////////////////////////
    // Vector functions - Created by Jurgen Westerhof 2024
    // https://turtletoy.net/turtle/d068ad6040
    ///////////////////////////////////////////////////////
    class Vector {
        static add  (a,b) { return a.map((v,i)=>v+b[i]); }
        static sub  (a,b) { return a.map((v,i)=>v-b[i]); }
        static mul  (a,b) { return a.map((v,i)=>v*b[i]); }
        static div  (a,b) { return a.map((v,i)=>v/b[i]); }
        static scale(a,s) { return a.map(v=>v*s); }
    
        static det(m)                { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); }
        static angle(a)              { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading
        static rot2d(angle)          { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; }
        static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; }
        static trans(matrix,a)       { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); }
        //Mirror vector a in a ray through [0,0] with direction mirror
        static mirror2d(a,mirror)    { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); }

        static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); }
        static norm  (a)     { return this.scale(a,1/this.len(a)); }
        static len   (a)     { return Math.hypot(...a); }
        static lenSq (a)     { return a.reduce((a,c)=>a+c**2,0); }
        static lerp  (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); }
        static dist  (a,b)   { return Math.hypot(...this.sub(a,b)); }
        
        static dot  (a,b)   { return a.reduce((a,c,i) => a+c*b[i], 0); }
        static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); }
    }
    this.V = Vector;
    
    class Intersection2D {
        //a-start, a-direction, b-start, b-direction
        //returns false on no intersection or [[intersection:x,y], scalar a-direction, scalar b-direction
        static info(as, ad, bs, bd) { const d = V.sub(bs, as), det = -V.det([bd, ad]); if(det === 0) return false; const res = [V.det([d, bd]) / det, V.det([d, ad]) / det]; return [V.add(as, V.scale(ad, res[0])), ...res]; }
        static ray(a, b, c, d) { return this.info(a, b, c, d); }
        static segment(a,b,c,d, inclusiveStart = true, inclusiveEnd = true) { const i = this.info(a, V.sub(b, a), c, V.sub(d, c)); return i === false? false: ( (inclusiveStart? 0<=i[1] && 0<=i[2]: 0<i[1] && 0<i[2]) && (inclusiveEnd?   i[1]<=1 && i[2]<=1: i[1]<1 && i[2]<1) )?i[0]:false;}
        static tour(tour, segmentStart, segmentDirection) { return tour.map((e, i, a) => [i, this.info(e, V.sub(a[(i+1)%a.length], e), segmentStart, segmentDirection)]).filter(e => e[1] !== false && 0 <= e[1][1] && e[1][1] <= 1).filter(e => 0 <= e[1][2]).map(e => ({position: e[1][0],tourIndex: e[0],tourSegmentPortion: e[1][1],segmentPortion: e[1][2],}));}
        static inside(tour, pt) { return tour.map((e,i,a) => this.segment(e, a[(i+1)%a.length], pt, [Number.MAX_SAFE_INTEGER, 0], true, false)).filter(e => e !== false).length % 2 == 1; }
        static circles(centerA, radiusA, centerB, radiusB) {const result = {intersect_count: 0,intersect_occurs: true,one_is_in_other: false,are_equal: false,point_1: [null, null],point_2: [null, null],};const dx = centerB[0] - centerA[0];const dy = centerB[1] - centerA[1];const dist = Math.hypot(dy, dx);if (dist > radiusA + radiusB) {result.intersect_occurs = false;}if (dist < Math.abs(radiusA - radiusB) && !N.approx(dist, Math.abs(radiusA - radiusB))) {result.intersect_occurs = false;result.one_is_in_other = true;}if (V.approx(centerA, centerB) && radiusA === radiusB) {result.are_equal = true;}if (result.intersect_occurs) {const centroid = (radiusA**2 - radiusB**2 + dist * dist) / (2.0 * dist);const x2 = centerA[0] + (dx * centroid) / dist;const y2 = centerA[1] + (dy * centroid) / dist;const prec = 10000;const h = (Math.round(radiusA**2 * prec)/prec - Math.round(centroid**2 * prec)/prec)**.5;const rx = -dy * (h / dist);const ry = dx * (h / dist);result.point_1 = [x2 + rx, y2 + ry];result.point_2 = [x2 - rx, y2 - ry];if (result.are_equal) {result.intersect_count = null;} else if (result.point_1.x === result.point_2.x && result.point_1.y === result.point_2.y) {result.intersect_count = 1;} else {result.intersect_count = 2;}}return result;}
    }
    this.Intersection = Intersection2D;
    
    class PathTools {
        static bezier(p1, cp1, cp2, p2, steps = null) {steps = (steps === null? (V.len(V.sub(cp1, p1)) + V.len(V.sub(cp2, cp1)) + V.len(V.sub(p2, cp2))) | 0: steps) - 1;return Array.from({length: steps + 1}).map((v, i, a, f = i/steps) => [[V.lerp(p1, cp1, f),V.lerp(cp1, cp2, f),V.lerp(cp2, p2, f)]].map(v => V.lerp(V.lerp(v[0], v[1], f), V.lerp(v[1], v[2], f), f))[0]);}
        // https://stackoverflow.com/questions/18655135/divide-bezier-curve-into-two-equal-halves#18681336
        static splitBezier(p1, cp1, cp2, p2, t=.5) {const e = V.lerp(p1, cp1, t);const f = V.lerp(cp1, cp2, t);const g = V.lerp(cp2, p2, t);const h = V.lerp(e, f, t);const j = V.lerp(f, g, t);const k = V.lerp(h, j, t);return [[p1, e, h, k], [k, j, g, p2]];}
        static circular(radius,verticeCount,rotation=0) {return Array.from({length: verticeCount}).map((e,i,a,f=i*2*Math.PI/verticeCount+rotation) => [radius*Math.cos(f),radius*Math.sin(f)])}
        static circle(r){return this.circular(r,Math.max(12, r*2*Math.PI|0));}
        static arc(radius, extend = 2 * Math.PI, clockWiseStart = 0, steps = null, includeLast = false) { return [steps == null? (radius*extend+1)|0: steps].map(steps => Array.from({length: steps}).map((v, i, a) => [radius * Math.cos(clockWiseStart + extend*i/(a.length-(includeLast?1:0))), radius * Math.sin(clockWiseStart + extend*i/(a.length-(includeLast?1:0)))])).pop(); }
        static draw(turtle, path) {path.forEach((pt, i) => turtle[i==0?'jump':'goto'](pt));}
        static drawTour(turtle, path) {this.draw(turtle, path.concat([path[0]]));}
        static drawPoint(turtle, pt, r = .1) {this.drawTour(turtle, this.circle(r).map(e => V.add(e, pt)));}
        static drawArrow(turtle, s, d, width = 6, length = 3) {turtle.jump(s);const arrowHeadBase = V.add(s,d);turtle.goto(arrowHeadBase);turtle.goto(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, width/2])));turtle.jump(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, -width/2])));turtle.goto(arrowHeadBase);}
    }
    
    this.PT = PathTools;
    
    class Complex {
        static add(a,b)     { return V.add(a,b); }
        static sub(a,b)     { return V.sub(a,b); }
        static scale(a,s)   { return V.scale(a,s); }
        static mult(a,b)    { return [a[0]*b[0]-a[1]*b[1],a[0]*b[1]+a[1]*b[0]]; }
        static sqrt(a)      { return [[Math.hypot(...a)**.5, Math.atan2(...a.reverse()) / 2]].map(ra => [ra[0]*Math.cos(ra[1]), ra[0]*Math.sin(ra[1])]).pop(); }
    }
    this.C = Complex;
    
    class Numbers {
        static approx(a,b,p)        { return Math.abs(a-b) < (p === undefined? .001: p); }
        static clamp(a, min, max)   { return Math.min(Math.max(a, min), max); }
    }
    this.N = Numbers;
}

////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// (Polygon binning by Lionel Lemarie 2021) https://turtletoy.net/turtle/95f33bd383
// (Delegated Hatching by Jurgen Westerhof 2024) https://turtletoy.net/turtle/d068ad6040
// (Deferred Polygon Drawing by Jurgen Westerhof 2024) https://turtletoy.net/turtle/6f3d2bc0b5
// https://turtletoy.net/turtle/a5befa1f8d
//
// const polygons = new Polygons();
// const p = polygons.create();
// polygons.draw(turtle, p);
// polygons.list();
// polygons.startDeferSession();
// polygons.stopDeferring();
// polygons.finalizeDeferSession(turtle);
//
// p.addPoints(...[[x,y],]);
// p.addSegments(...[[x,y],]);
// p.addOutline();
// p.addHatching(angle, distance); OR p.addHatching(HatchObject); where HatchObject has a method 'hatch(PolygonClass, thisPolygonInstance)'
// p.inside([x,y]);
// p.boolean(polygon, diff = true);
// p.segment_intersect([x,y], [x,y], [x,y], [x,y]);
////////////////////////////////////////////////////////////////
function Polygons(){const t=[],s=25,e=Array.from({length:s**2},t=>[]),n=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,n=1e5,h=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),n=Math.min(n,t[1]),h=Math.max(h,t[1])}),this.aabb=[s,n,e,h]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,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, s) {if(typeof t == 'object') return t.hatch(n, this);const e=new n;e.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const h=Math.sin(t)*s,o=Math.cos(t)*s,a=200*Math.sin(t),i=200*Math.cos(t);for(let t=.5;t<150/s;t++) {e.dp.push([h*t+i,o*t-a],[h*t-i,o*t+a]);e.dp.push([-h*t+i,-o*t-a],[-h*t-i,-o*t+a]);}e.boolean(this,!1);this.dp=[...this.dp,...e.dp]}inside(t){let s=0;for(let e=0,n=this.cp.length;e<n;e++)this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%n])&&s++;return 1&s}boolean(t,s=!0){const e=[];for(let n=0,h=this.dp.length;n<h;n+=2){const h=this.dp[n],o=this.dp[n+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const n=this.segment_intersect(h,o,t.cp[s],t.cp[(s+1)%e]);!1!==n&&a.push(n)}if(0===a.length)s===!t.inside(h)&&e.push(h,o);else{a.push(h,o);const n=o[0]-h[0],i=o[1]-h[1];a.sort((t,s)=>(t[0]-h[0])*n+(t[1]-h[1])*i-(s[0]-h[0])*n-(s[1]-h[1])*i);for(let n=0;n<a.length-1;n++)(a[n][0]-a[n+1][0])**2+(a[n][1]-a[n+1][1])**2>=.001&&s===!t.inside([(a[n][0]+a[n+1][0])/2,(a[n][1]+a[n+1][1])/2])&&e.push(a[n],a[n+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,n){const h=(n[1]-e[1])*(s[0]-t[0])-(n[0]-e[0])*(s[1]-t[1]);if(0===h)return!1;const o=((n[0]-e[0])*(t[1]-e[1])-(n[1]-e[1])*(t[0]-e[0]))/h,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/h;return o>=0&&o<=1&&a>=0&&a<=1&&[t[0]+o*(s[0]-t[0]),t[1]+o*(s[1]-t[1])]}};const y=function(n,j=[]){const h={},o=200/s;for(var a=0;a<s;a++){const c=a*o-100,r=[0,c,200,c+o];if(!(n[3]<r[1]||n[1]>r[3]))for(var i=0;i<s;i++){const c=i*o-100;r[0]=c,r[2]=c+o,n[0]>r[2]||n[2]<r[0]||e[i+a*s].forEach(s=>{const e=t[s];n[3]<e.aabb[1]||n[1]>e.aabb[3]||n[0]>e.aabb[2]||n[2]<e.aabb[0]||j.includes(s)||(h[s]=1)})}}return Array.from(Object.keys(h),s=>t[s])};return{list:()=>t,create:()=>new n,draw:(n,h,o=!0)=>{rpl=y(h.aabb, this.dei === undefined? []: Array.from({length: t.length - this.dei}).map((e, i) => this.dsi + i));for(let t=0;t<rpl.length&&h.boolean(rpl[t]);t++);const td=n.isdown();if(this.dsi!==undefined&&this.dei===undefined)n.pu();h.draw(n),o&&function(n){t.push(n);const h=t.length-1,o=200/s;e.forEach((t,e)=>{const a=e%s*o-100,i=(e/s|0)*o-100,c=[a,i,a+o,i+o];c[3]<n.aabb[1]||c[1]>n.aabb[3]||c[0]>n.aabb[2]||c[2]<n.aabb[0]||t.push(h)})}(h);if(td)n.pd();},startDeferSession:()=>{if(this.dei!==undefined)throw new Error('Finalize deferring before starting new session');this.dsi=t.length;},stopDeferring:()=>{if(this.dsi === undefined)throw new Error('Start deferring before stopping');this.dei=t.length;},finalizeDeferSession:(n)=>{if(this.dei===undefined)throw new Error('Stop deferring before finalizing');for(let i=this.dsi;i<this.dei;i++) {rpl = y(t[i].aabb,Array.from({length:this.dei-this.dsi+1}).map((e,j)=>i+j));for(let j=0;j<rpl.length&&t[i].boolean(rpl[j]);j++);t[i].draw(n);}this.dsi=undefined;this.dei=undefined;}}}

///////////////////////////////////////////////////////////////////
// Polygon Hatching utility code - Created by Jurgen Westerhof 2024
// https://turtletoy.net/turtle/d068ad6040
// ////////////////////////////////////////////////////////////////
// // To be used with modified Polygon Clipping utility code
// //            Orginal: https://turtletoy.net/turtle/a5befa1f8d
// //    Polygon binning: https://turtletoy.net/turtle/95f33bd383
// // Delegated Hatching: https://turtletoy.net/turtle/d068ad6040
///////////////////////////////////////////////////////////////////
function loadHatcheryNamespace() {
    //for convenience on Turtletoy you can click the arrow to the right of the line number to collapse a class
    //////////////////////////////////////////////////// root
    class PolygonHatching {
        constructor() {
            if (this.constructor === PolygonHatching) {
                throw new Error("PolygonHatching is an abstract class and can't be instantiated.");
            }
            
            this.minX = -100;
            this.minY = -100;
            this.maxX = 100;
            this.maxY = 100;
            this.width = 200;
            this.height = 200;
            
            this.segments = [];
            
            this.init();
        }
        hatch(polygonsClass, thePolygonToHatch) {
            const e = new polygonsClass;
            e.cp.push(...thePolygonToHatch.aabb);//[-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);
            
            this.addHatchSegments(e.dp);
            
            e.boolean(thePolygonToHatch,!1);
            thePolygonToHatch.dp=[...thePolygonToHatch.dp,...e.dp]
        }
        addHatchSegments(segments) {
            this.getSegments().forEach(e => segments.push(e));
        }
        getSegments() { return this.segments; }
        init() {
            ///////////////////////////////////////////////////////
            // Vector functions - Created by Jurgen Westerhof 2024
            // https://turtletoy.net/turtle/d068ad6040
            ///////////////////////////////////////////////////////
            class Vector {
                static add  (a,b) { return a.map((v,i)=>v+b[i]); }
                static sub  (a,b) { return a.map((v,i)=>v-b[i]); }
                static mul  (a,b) { return a.map((v,i)=>v*b[i]); }
                static div  (a,b) { return a.map((v,i)=>v/b[i]); }
                static scale(a,s) { return a.map(v=>v*s); }
            
                static det(m)                { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); }
                static angle(a)              { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading
                static rot2d(angle)          { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; }
                static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; }
                static trans(matrix,a)       { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); }
                //Mirror vector a in a ray through [0,0] with direction mirror
                static mirror2d(a,mirror)    { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); }
        
                static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); }
                static norm  (a)     { return this.scale(a,1/this.len(a)); }
                static len   (a)     { return Math.hypot(...a); }
                static lenSq (a)     { return a.reduce((a,c)=>a+c**2,0); }
                static lerp  (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); }
                static dist  (a,b)   { return Math.hypot(...this.sub(a,b)); }
                
                static dot  (a,b)   { return a.reduce((a,c,i) => a+c*b[i], 0); }
                static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); }
            }
            this.V = Vector;
            
            class Intersection2D {
                //a-start, a-direction, b-start, b-direction
                //returns false on no intersection or [[intersection:x,y], scalar a-direction, scalar b-direction
                static info(as, ad, bs, bd) {
                    const d = V.sub(bs, as), det = -V.det([bd, ad]);
                    if(det === 0) return false;
                    const res = [V.det([d, bd]) / det, V.det([d, ad]) / det];
                    return [V.add(as, V.scale(ad, res[0])), ...res];
                }
                static ray(a, b, c, d) {
                    return this.info(a, b, c, d);
                }
                static segment(a,b,c,d, inclusiveStart = true, inclusiveEnd = true) {
                    const i = this.info(a, V.sub(b, a), c, V.sub(d, c));
                    return i === false? false: (
                        (inclusiveStart? 0<=i[1] && 0<=i[2]: 0<i[1] && 0<i[2])
                     && (inclusiveEnd?   i[1]<=1 && i[2]<=1: i[1]<1 && i[2]<1)
                    )?i[0]:false;
                }
            }
            this.Intersection = Intersection2D;
        }
    }
    this.PolygonHatching = PolygonHatching;
    //////////////////////////////////////////////////// first gen
    // extending PolygonHatching
    class CircularHatching extends PolygonHatching {
        constructor(distance = 360, center = [0,0], precision = 1) {
            super();
            const dist = typeof distance == 'function'? distance: (c) => distance;
            
            this.segments = [];
            for(let j = 0, r = dist(j)/2; r < 201; r+=dist(j++)) {
                for(let i = 0, max = Math.max(12, 2*Math.PI*r/precision | 0); i < max; i++) {
                    this.segments.push(
                        [center[0]+r*Math.sin(i*2*Math.PI/max),center[1]+r*-Math.cos(i*2*Math.PI/max)],
                        [center[0]+r*Math.sin((i+1)*2*Math.PI/max),center[1]+r*-Math.cos((i+1)*2*Math.PI/max)]
                    );
                }
            }
        }
    }
    this.CircularHatching = CircularHatching;
    class GridHatching extends PolygonHatching {
        constructor(cellSize, angle = 0) {
            super();
    
            if (this.constructor === GridHatching) {
                throw new Error("GridHatching is an abstract class and can't be instantiated.");
            }
            
            this.cellSize = cellSize;
            this.rotation = this.V.rot2d(angle);
            
            this.grid = ((this.width * 1.5 / cellSize) | 0) + 1;
    
            this.cells = this.grid**2;
            
            this.segments = [];
        }
        getCell(n) {
            if(n > this.cells) return false;
            const col = n % this.grid;
            const row = n / this.grid | 0;
            return [
                (col + .5) - (this.grid / 2), 
                (row + .5) - (this.grid / 2), 
            ];
        }
        oddness(cellN, n = 2) {
            return ((cellN % this.grid) // column
                    + (cellN / this.grid | 0)) // row
                % n;
        }
        getSegments() {
            if(this.segments.length == 0) {
                const segmentsSet = this.getSegmentsSet();
                for(let i = 0; i < this.cells; i++) {
                    segmentsSet[this.oddness(i, segmentsSet.length)].forEach(segment => {
                        const cellLocation = this.getCell(i);
                        this.segments.push(
                            this.V.trans(this.rotation, this.V.scale(this.V.add(cellLocation, segment[0]), this.cellSize)),
                            this.V.trans(this.rotation, this.V.scale(this.V.add(cellLocation, segment[1]), this.cellSize))
                        );
                    });
                }
            }
            return this.segments;
        }
        getSegmentsSet() {
            return [this.getTileSegments()].flatMap(ts => [ts, ts.map(segment => segment.map(pt => [...pt].reverse()))]);
        }
    }
    this.GridHatching = GridHatching;
    class LaticeHatching extends PolygonHatching {
        constructor(angle = 0, xUnit = [10, 0], yUnit = [0, 5]) {
            super();
            this.tile = [[...xUnit], [...yUnit]];
            this.rotation = this.V.rot2d(angle);
        }
        setLaticeSegments(info) {
            for(let i = 0, max = info.columns * info.rows - 1; i < max; i++) {
                const col = i % info.columns;
                const row = i / info.columns | 0;
                
                const position = this.V.add(this.V.scale(info.rowIncrement, row - info.rows / 2 + .5), this.V.scale(info.columnIncrement, col - info.columns / 2));
                info.lines.forEach(e => { this.segments.push(
                    this.V.trans(this.rotation, this.V.add(e[0], position)),
                    this.V.trans(this.rotation, this.V.add(e[1], position))
                )});
            }
        }
    }
    this.LaticeHatching = LaticeHatching;
    class LineHatching extends PolygonHatching {
        constructor(angle, distance, inp = 0) {
            super();
            
            const h=Math.sin(angle)*distance,o=Math.cos(angle)*distance,a=200*Math.sin(angle),i=200*Math.cos(angle);
            this.segments = Array.from({length: 150/distance}).flatMap((x,y,z,t=.5+y) => [
                [h*t+i+inp*(Math.random()-.5),o*t-a+inp*(Math.random()-.5)],[h*t-i+inp*(Math.random()-.5),o*t+a+inp*(Math.random()-.5)], [-h*t+i+inp*(Math.random()-.5),-o*t-a+inp*(Math.random()-.5)],[-h*t-i+inp*(Math.random()-.5),-o*t+a+inp*(Math.random()-.5)]
            ])
        }
    }
    this.LineHatching = LineHatching;
    class PoissonDiscHatching extends PolygonHatching {
        constructor(radius = 1.75, connect = false, randomGrowOrder = .1, loosePacking = .5, startPoint = [0,0]) {
            super();
            
            ////////////////////////////////////////////////////////////////
            // Poisson-Disc utility code. Created by Reinder Nijhoff 2019
            // https://turtletoy.net/turtle/b5510898dc
            ////////////////////////////////////////////////////////////////
            function PoissonDisc(startPoints, radius) {
                class PoissonDiscGrid {
                    constructor(sp, radius) {
                        this.cellSize = 1/Math.sqrt(2)/radius;
                        this.radius2 = radius*radius;
                        this.cells = [];
                        sp.forEach( p => this.insert(p) );
                    }
                    insert(p) {
                        const x = p[0]*this.cellSize|0, y=p[1]*this.cellSize|0;
                        for (let xi = x-1; xi<=x+1; xi++) {
                            for (let yi = y-1; yi<=y+1; yi++) {
                                const ps = this.cell(xi,yi);
                                for (let i=0; i<ps.length; i++) {
                                    if ((ps[i][0]-p[0])**2 + (ps[i][1]-p[1])**2 < this.radius2) {
                                        return false;
                                    }
                                }
                            }       
                        }
                        this.cell(x, y).push(p);
                        return true;
                    }
                    cell(x,y) {
                        const c = this.cells;
                        return (c[x]?c[x]:c[x]=[])[y]?c[x][y]:c[x][y]=[];
                    }
                }
                class PoissonDisc {
                    constructor(sp, radius) {
                        this.result = [...sp];
                        this.active = [...sp];
                        this.grid = new PoissonDiscGrid(sp, radius);
                    }
                    addPoints(count, maxTries=16, loosePacking=0, randomGrowOrder=0) {
                    	mainLoop: while (this.active.length > 0 && count > 0) {
                    		const index = (Math.random() * this.active.length * randomGrowOrder) | 0;
                    		const point = this.active[index];
                    		for (let i=0; i < maxTries; i++) {
                    			const a = Math.random() * 2 * Math.PI;
                    			const d = (Math.random()*loosePacking + 1) * radius;
                    			const p = [point[0] + Math.cos(a)*d, point[1] + Math.sin(a)*d, point];
                    			if (this.grid.insert(p)) {
                        			this.result.push(p);
                        			this.active.push(p);
                        			count--;
                        			continue mainLoop;
                    			}
                    		}
                		    this.active.splice(index, 1);
                    	}
                    	return this.result;
                    }
                }
                return new PoissonDisc(startPoints, radius);
            }
    
            const disc = new PoissonDisc([startPoint], radius);
            let index = 0;
            
            const maxR = Math.max(
                this.V.lenSq(this.V.sub([this.minX, this.minY], startPoint)),
                this.V.lenSq(this.V.sub([this.maxX, this.minY], startPoint)),
                this.V.lenSq(this.V.sub([this.minX, this.maxY], startPoint)),
                this.V.lenSq(this.V.sub([this.maxX, this.maxY], startPoint))
            );
    
            while(disc.active.some(pt => this.V.lenSq([pt[0], pt[1]]) < maxR)) {
                const points = disc.addPoints(1, 32, loosePacking, randomGrowOrder);
            
                while (index<points.length) {
                    if (points[index][2]) {
                        this.segments.push(
                            points[index],
                            connect? points[index][2]: this.V.add(points[index], [0,.25])
                        )
                    }
                    index++;
                }
            }
        }
    }
    this.PoissonDiscHatching = PoissonDiscHatching;
    class RadialHatching extends PolygonHatching {
        constructor(n = 30, origin = [0,0], phaseExp = 1, phaseMod = 1, ampMod = 2, angle = 0) {
            super();

            function redistributeLinearPath(path, length = .5) {path = path.map(pt => [...pt]); const sub  = (a,b)   => a.map((v,i)=>v-b[i]);const len  = (a)     => Math.hypot(...a);const lerp = (a,b,t) => a.map((v, i) => v*(1-t) + b[i]*t);const result = [path[0]];const vectors = Array.from({length: path.length - 1}).map((e, i) => [path[i], len(sub(path[i+1], path[i]))]);done: {for(let i = 0; i < vectors.length; i++) {let l = length;while(l > 0 && i < vectors.length) {l-=vectors[i][1];i++;if(i == vectors.length) break done;}i--;[lerp(path[i], path[i+1], -l/vectors[i][1])].forEach(v => {result.push(v);path[i] = v;vectors[i] = [v, len(sub(path[i+1], v))];});}}result.push(path[path.length - 1]);return result;}
            
            const rotation = this.V.rot2d(angle);
            const diameter = (1.5 + 2*this.V.len(origin)/this.width) * this.width;

            const path = redistributeLinearPath(Array.from({length: ((diameter / 2) ** 2| 0) + 1}).map((e, i) => this.V.trans(this.V.rot2d(this.getX(i**.5, phaseExp, phaseMod) * Math.PI * ampMod / n), [i**.5, 0])), this.getRedistributionPrecision());

            for(let i = 0; i < n; i++) {
                const currentPath = path.map(e => this.V.trans(rotation, this.V.add(origin, this.V.trans(this.V.rot2d(i * 2 * Math.PI / n), e))));
                for(let i = 0; i < currentPath.length - 1; i++) {
                    this.segments.push(currentPath[i], currentPath[i+1]);
                }
            }
        }
        getX(i, phaseExp, phaseMod) {
            return 0;
        }
        getRedistributionPrecision() {
            return .25;
        }
    }
    this.RadialHatching = RadialHatching;
    class SineHatching extends PolygonHatching {
        constructor(waveLength = 5, amplitude = 1, angle = 0, distance = 1, samplesPerWavelength = waveLength * amplitude) {
            super();
            const rotation = this.V.rot2d(angle);
    
            const oneWaveLengthPath = Array.from({length: samplesPerWavelength}).map((v, i, a, f = i/a.length) => [f*waveLength, Math.sin(f*2*Math.PI) * amplitude]);
            const halfNWaveLengths = (this.maxX * 1.5 / waveLength | 0) + 1;
            const fullWave = Array.from({length: halfNWaveLengths * 2})
                                  .map((v, i) => [(i - halfNWaveLengths) * waveLength, 0])
                                  .flatMap(v => oneWaveLengthPath.map(pt => this.V.add(v, pt)))
                
                                  .map((v, i, a) => [v, a[(i+1)%a.length]]);
            fullWave.pop();
            
            for(let y = ((this.minY * 1.5) / distance | 0) - 1; y < ((this.maxY * 1.5) / distance | 0) + 1; y++) {
                fullWave.forEach(segment => {
                    this.segments.push(
                        this.V.trans(rotation, this.V.add([0, y * distance], segment[0])),
                        this.V.trans(rotation, this.V.add([0, y * distance], segment[1]))
                    );
                });
            }
        }
    }
    this.SineHatching = SineHatching;
    //////////////////////////////////////////////////// second gen
    // extending GridHatching
    class TreadPlateHatching extends GridHatching {
        constructor(size = 7, width = .4, length = .8, angle = 0, treads = 2, skew = 0) {
            super(size, angle);
            this.width = width;
            this.length = length;
            this.treads = treads;
            this.skew = skew;
        }
        getTileSegments() {
            const l = (2-this.width) * this.length;
            const w = this.width/(2*this.treads -1);
            const r = (w**2 + l**2) / (4*w);
    
            const skewAngle = (this.skew/10)*2*Math.PI;
            const rotationSkewMatrix = this.V.rot2d(skewAngle);
            
            const alpha = Math.asin(l/(2*r));
            const steps = (this.cellSize * 2) | 0;
            
            const arc = [];
            for(let i = 0; i < steps; i++) {
                arc.push([
                    r * Math.cos(Math.PI/2 - alpha + (i * 2 * alpha / steps)),
                    r * (Math.sin(Math.PI/2 - alpha + (i * 2 * alpha / steps)) - Math.sin(Math.PI/2 - alpha))
                ])
            }
            arc.forEach(pt => arc.push([-pt[0], -pt[1]]));
            const path = arc.map(pt => [this.V.trans(rotationSkewMatrix, pt)].map(ppt => [pt[0], ppt[1]]).pop())
            
            const paths = [];
            for(let i = 0; i < this.treads; i++) {
                paths.push(path.map(pt => [pt[0], pt[1] - ((this.treads-1) * w) + (2*i*w)]));
            }
            
            return paths.flatMap(path => path.map((pt, i, a) => [pt, a[(i+1)%a.length]]));
        }
    }
    this.TreadPlateHatching = TreadPlateHatching;
    class WeaveHatching extends GridHatching {
        constructor(size = 7, width = .7, length = 1, angle = 0, threads = 1, skew = 0, closeSegments = true) {
            super(size, angle);
            this.width = width;
            this.length = length;
            this.threads = threads;
            this.skew = skew;
            this.closeSegments = closeSegments;
        }
        getTileSegments() {
            const skewAngle = (this.skew/10)*2*Math.PI;
            const rotationSkewMatrix = this.V.rot2d(skewAngle);
    
            const path = [
                [(-.5 - (1-this.width)/2) * this.length, -this.width/2],
                                [(.5 + (1-this.width)/2) * this.length, -this.width/2],
                                [(.5 + (1-this.width)/2) * this.length,  this.width/2],
                [(-.5 - (1-this.width)/2) * this.length, this.width/2]
            ].map((pt, i) => this.V.add(pt, [(i < 2? .5:-.5)*Math.sin(skewAngle), 0]))
            .map(pt => this.V.trans(rotationSkewMatrix, pt));
            
            const segments = path.map((pt, i, a) => [pt, a[(i+1)%a.length]])
                                 .map((segment, i, a) => i < a.length/2? segment: [...segment.reverse()]);
            
            const hSegments = segments.filter((v, i) => i%2 == 0);
            const vSegments = segments.filter((v, i) => i%2 == 1 && (this.length < 1 || this.skew != 0));
    
            return Array.from({length: this.threads+1}).map((e, i) => [this.V.lerp(hSegments[0][0], hSegments[1][0], i/this.threads), this.V.lerp(hSegments[0][1], hSegments[1][1], i/this.threads)]).concat(this.closeSegments? vSegments: []);
        }
    }
    this.WeaveHatching = WeaveHatching;
    // extending LaticeHatching
    class ChevronHatching extends LaticeHatching {
        constructor(angle, tile, offset = 0, margin = 0) {
            super(angle, ...tile);
            
            this.setLaticeSegments(this.getInfo(offset, margin));
        }
        getInfo(offset, margin) {
            const mirror = this.V.mirror2d(...this.tile);
            const vOffset = this.V.scale(this.tile[1], offset);
            const vMargin = this.tile.map(v => this.V.scale(v, margin/this.V.len(v)/2));
            
            const lines = [...this.tile.map((v, i) => [[0,0], this.V.sub(v, this.V.scale(vMargin[i], 2))])];
            if(margin == 0) {
                lines.push([[0,0], mirror].map(e => this.V.add(vOffset, e)));
                lines.push(lines[1].map(pt => this.V.add(pt, lines[2][1])));
            } else {
                lines.push(lines[0].map(v => this.V.add(lines[1][1], v)));
                lines.push(lines[1].map(v => this.V.add(lines[0][1], v)));
        
                [this.V.add(...vMargin)].forEach(os => lines.forEach((e, i) => lines[i] = lines[i].map(pt => this.V.add(pt, os))) );
                lines.forEach(l => lines.push(l.map(pt => this.V.add(vOffset, this.V.mirror2d(pt, this.tile[1])))));
            }
            
            return {
                lines: lines,
                columns: ((this.width * 1.5 + this.V.len(this.V.add(this.V.add(vOffset, mirror), this.tile[0]))) / this.V.len(this.tile[1]) | 0) + 2,
                rows: (this.width * 1.5 / this.V.len(this.V.sub(this.tile[0], mirror)) | 0) + 2,
                columnIncrement: [...this.tile[1]],
                rowIncrement: this.V.add(this.V.scale(this.tile[0], -1), mirror),
            }
        }
    }
    this.ChevronHatching = ChevronHatching;
    class HerringboneHatching extends LaticeHatching {
        constructor(angle, tile, poly = 1) {
            super(angle, ...tile);
            
            this.setLaticeSegments(this.getInfo(poly));
        }
        getInfo(poly) {
            const otherTile = this.tile.map((e, i, a) => this.V.scale(e, this.V.len(a[(i+1)%a.length])/this.V.len(e)))
            
            const colInc = this.V.add(this.tile[1], otherTile[0]);
            const rowInc = this.V.sub(otherTile[1], this.tile[0]);
            
            const lines = [[this.V.scale(otherTile[0], -1), this.tile[0]], [[0,0], this.V.add(this.tile[1], otherTile[1])]];
            
            const colWidth = this.V.len(colInc);
            const colStartOffset = colWidth / 2 + this.V.len(this.V.sub(...lines[0]));
            const rowHeight = this.V.len(rowInc);
            
            for(let i = 1; i < poly; i++) {
                lines.push([this.V.scale(this.tile[1]     , i /  poly)].map(v => [v, this.V.add(v, this.tile[0]     )]).pop());
                lines.push([this.V.scale(otherTile[0], i / -poly)].map(v => [v, this.V.add(v, otherTile[1])]).pop());
            }
            
            return {
                lines: lines,
                columns: ((this.width * 1.5 + colStartOffset) / colWidth | 0) + 2,
                rows: (this.width * 1.5 / rowHeight | 0) + 2,
                columnIncrement: colInc,
                rowIncrement: rowInc,
            }
        }
    }
    this.HerringboneHatching = HerringboneHatching;
    class HexGridHatching extends LaticeHatching {
        constructor(angle = 0, size = 10, cellSize = 1) {
            const vSize = size/2;
            const hSize = vSize*3**.5/2;
    
            super( angle, ...[[hSize, 1.5*vSize], [2*hSize, 0]] );
            
            this.vSize = vSize;
            this.hSize = hSize;
            
            this.setLaticeSegments(this.getInfo(cellSize));
        }
        getInfo(cellSize) {
            return {
                lines: this.getPath()
                           .map(pt => this.V.scale(pt, cellSize))
                           .map((e,i,a) => [e, a[(i+1)%a.length]])
                           .filter((e,i) => cellSize < 1 || i < 3),
                columns: this.width * 1.5 / this.V.len(this.tile[0]) | 0,
                rows: this.height * 1.5 / this.V.len(this.tile[1]) | 0,
                columnIncrement: [...this.tile[1]],
                rowIncrement: [...this.tile[0]],
            }
        }
        getPath() {
            return [
                [-this.hSize,  this.vSize/2],
                [-this.hSize, -this.vSize/2],
                [          0, -this.vSize  ],
                [ this.hSize, -this.vSize/2],
                [ this.hSize,  this.vSize/2],
                [          0,  this.vSize  ]
            ];
        }
    }
    this.HexGridHatching = HexGridHatching;
    // extending RadialHatching
    class BlockRadialHatching extends RadialHatching {
        constructor(n = 30, origin = [0,0], phaseExp = 1, phaseMod = 1, ampMod = 1, angle = 0) {
            super(n, origin, phaseExp, phaseMod, ampMod, angle);
        }
        getX(i, phaseExp, phaseMod) {
            return Math.sign(Math.sin(i**phaseExp*phaseMod));
        }
        getRedistributionPrecision() {
            return .1;
        }
    }
    this.BlockRadialHatching = BlockRadialHatching;
    class SineRadialHatching extends RadialHatching {
        getX(i, phaseExp, phaseMod) {
            return Math.sin(i**phaseExp*phaseMod);
        }
    }
    this.SineRadialHatching = SineRadialHatching;
    class SpiralHatching extends RadialHatching {
        getX(i, phaseExp, phaseMod) {
            return i**phaseExp*phaseMod;
        }
    }
    this.SpiralHatching = SpiralHatching;
    class ZigZagRadialHatching extends RadialHatching {
        getX(i, phaseExp, phaseMod) {
            return 2 * Math.asin(Math.sin(i**phaseExp*phaseMod)) / Math.PI;
        }
    }
    this.ZigZagRadialHatching = ZigZagRadialHatching;
    // extending SineHatching
    class ZigZagHatching extends SineHatching {
        constructor(waveLength = 5, amplitude = 2, angle = 0, distance = 1) {
            super(waveLength, amplitude, angle, distance, 4);
        }
    }
    this.ZigZagHatching = ZigZagHatching;
    //////////////////////////////////////////////////// third gen
    // extending ChevronHatchting
    class BrickHatching extends ChevronHatching {
        constructor(angle = 0, width = 5, height = 2.5, margin = 0) {
            super(angle, [[0,height], [width,0]], .5, margin);
        }
    }
    this.BrickHatching = BrickHatching;
    // extending HexGridHatching
    class HoneyCombHatching extends HexGridHatching {
        constructor(angle, hexSize, drawSize = .8) {
            super(angle, hexSize, drawSize);
        }
    }
    this.HoneyCombHatching = HoneyCombHatching;
    class QBertHatching extends HexGridHatching {
        getInfo() {
            return {
                lines: this.getPath().map(e => [[0, 0], e]),
                columns: this.width * 1.5 / Math.max(this.tile[0][0], this.tile[1][0]) | 0,
                rows: this.height * 1.5 / Math.max(this.tile[0][1], this.tile[1][1]) | 0,
                columnIncrement: [...this.tile[1]],
                rowIncrement: [...this.tile[0]],
            }
        }
    }
    this.QBertHatching = QBertHatching;
    class TriangleHatching extends HexGridHatching {
        getPath() {
            return super.getPath().filter((e,i) => i%2 == 1);
        }
        getInfo(cellSize) {
            const info = super.getInfo(cellSize);
            info.columns *= 2;
            info.rows *= 2;
            return info;
        }
    }
    this.TriangleHatching = TriangleHatching;
    // extending TreadPlateHatching
    class PolkaDotHatching extends TreadPlateHatching {
        constructor(angle = 0, distance = 5, radius = 1) {
            super(distance, 2 * (radius+(radius == distance? .00001: 0))/distance, radius/(distance-radius+(radius == distance? .00001: 0)), angle, 1);
        }
    }
    this.PolkaDotHatching = PolkaDotHatching;
}