Retteketet Tuiles de Truchet 💠

Single scale, multi scale, bezier, framed, unframed, with or without padding: we got it all! Look around and play with the settings. You break you buy. Like what you see? Order one now and get two for free! With our 30 day no good money back guarantee you're completely risk free!

Retteketet Tuiles de Truchet 💠 (variation)

In response to Truchet Tiles 004 we bring you Retteketet Tuiles de Truchet 💠 (variation)

Log in to post a comment.

const dimension = 60; //min=3 max=150 step=1
const pCurved = 1; //min=0 max=1 step=.01
const maxSize = 10; //min=1 max=30 step=1
const style = 0; //min=0 max=1 step=1 (Random, Regular)
const curvesBy = 0; //min=0 max=1 step=1 (Circle, Bezier)
const ifBezierThen = 0; //min=0 max=1 step=1 (Bezier control amplitude is constant, Bezier control amplitude is random per tile)
const constantBezier = 1; //min=0 max=1 step=.01
const drawFrame = 1; //min=0 max=1 step=1 (No, Yes)
const frameSize = 5; //min=0 max=50 step=1
const rndAttempts = 40 //min=1 max=100 step=1
const margin = 0; //min=0 max=.5 step=.01
const drawBorders = 0; //min=0 max=1 step=1 (No, Yes)

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

// Global code will be evaluated once.
const turtle = new Turtle();

const grid = new Grid(200 - 2 * frameSize, dimension);

let size = Math.min(maxSize, dimension);//10;

if(drawFrame) drawTour(turtle, [[frameSize-100, frameSize-100], [frameSize-100, 100-frameSize], [100-frameSize, 100-frameSize], [100-frameSize, frameSize-100]]);

// The walk function will be called until it returns false.
function walk(i) {
    return style == 0? multiscale(i): monoscale(i);
}

function monoscale(i) {
    const tilesPerRow = dimension / maxSize | 0;
    const margin = (dimension % (tilesPerRow * maxSize)) / 2 | 0;
    
    const column = i % tilesPerRow;
    const row = i / tilesPerRow | 0;
    
    grid.addTile(margin + column * maxSize, margin + row * maxSize, maxSize);
    
    if(i == tilesPerRow**2 - 1) {
        if(grid.getFreeCells().length > 0) {
            if(margin > 0) {
                for(let c = margin; c + margin < dimension; c += margin) {
                    grid.addTile(c, 0, margin);
                    grid.addTile(margin + tilesPerRow * maxSize, c, margin);
                    grid.addTile(margin + tilesPerRow * maxSize - c, margin + tilesPerRow * maxSize, margin);
                    grid.addTile(0, margin + tilesPerRow * maxSize - c, margin);
                }
            }
            grid.getFreeCells().forEach(c => grid.addTile(...c, 1));
        }
        return false;
    }
    
    return true;
}

function multiscale(i) {
    let tries = rndAttempts;
    while(tries-- > 0) {
        const column = (Math.random() * (dimension - size + 1)) | 0;
        const row = (Math.random() * (dimension - size + 1)) | 0;

        if(grid.canFit(column, row, size)) {
            grid.addTile(column, row, size);
            return true;
        }
    }
    
    if(size == 1) {
        grid.getFreeCells().forEach(c => grid.addTile(...c, 1));
    
        return false;
    }
    size--;
    return true;
}

function Tile(position, size, scale) {
    class Tile {
        constructor(position, size, scale) {
            this.position = position;
            this.size = size;
            this.scale = scale;
        }
        draw(turtle) {
            const border = [[-1, -1], [1, -1], [1, 1], [-1, 1]].flatMap((v, i, a) => i == a.length - 1? [v, a[0]]: [v]).map(pt => scale2(pt, this.size * this.scale / 2));
            const content = Math.random() > pCurved? this.getStraightPaths(): (curvesBy == 0? this.getCurvedPaths(): this.getBezeirPaths());
            
            [drawBorders? border: [], ...content].forEach(path => drawPath(turtle,    path.map(pt => scale2(pt, 1 - margin)).map(pt => add2(pt, this.position))) );
        }
        getStraightPaths() {
            const rot = rot2(((Math.random() * 2) | 0) * Math.PI / 2);
            
            const paths = [];
            for(let i = 0; i <= this.size; i++) {
                paths.push([
                    [-this.size * this.scale/2, i * this.scale - this.size * this.scale/2],
                    [this.size * this.scale/2, i * this.scale - this.size * this.scale/2]
                ]);
            }
            
            return paths.map(path => path.map(pt => trans2(rot, pt)));
        }
        getBezeirPaths() {
            const rot = rot2(((Math.random() * 4) | 0) * Math.PI / 2);
            
            const randomBezier = [Math.random(), Math.random()];
            
            const paths = [];        
            for(let i = 1; i <= this.size; i++) {
                const s = i * this.scale;
                
                const baseA = [s, 0];
                const controlA = [0, (ifBezierThen == 0? constantBezier: randomBezier[0])  * s];
                const baseB = [0, s];
                const controlB = [(ifBezierThen == 0? constantBezier: randomBezier[1]) * s, 0];
                
                const iteration = Math.min(.1, 1 / s);
                const path = [];
                for(let j = 0; j < 1 - iteration / 2; j+=iteration) {
                    const alt1 = add2(baseA, scale2(controlA, j));
                    const alt2 = add2(baseB, scale2(controlB, 1-j));
                    path.push(add2(alt1, scale2(sub2(alt2, alt1), j)));
                }
                path.push(baseB);
                
                paths.push(path.map(pt => add2(pt, [this.size * this.scale / -2, this.size * this.scale / -2])));
            }
            const theBigOne = paths.pop();

            const m = rot2(Math.PI);
            const shadowed = paths.map(path => path.map(pt => trans2(m, pt)));
            paths.push(theBigOne);
            
            if(paths.length > 0) {
                for(let i = 0; i < shadowed.length; i++) {
                    const intersections = pathsIntersections(theBigOne, shadowed[i]);
                    if(intersections.length < 2) {
                        paths.push(shadowed[i]);
                    } else {
                        const filtered = shadowed[i].filter((v, i) => i < intersections[1][2]);
                        paths.push((filtered.length == 0? [shadowed[i][0]]:filtered).concat([intersections[1][0]]));
                        paths.push([intersections[0][0]].concat(shadowed[i].filter((v, i) => i > intersections[0][2])));
                    }
                }
            }

            return paths.map(path => path.map(pt => trans2(rot, pt)));
        } 
        getCurvedPaths() {
            const rot = rot2(((Math.random() * 4) | 0) * Math.PI / 2);
            
            const paths = [];
            
            const convert = (cps, m = 1) => cps.map(pt => add2(pt, scale2([-this.size * this.scale/2, -this.size * this.scale/2], m)));
            const dp = (cps) => drawPath(turtle, cps.map(pt => add2(pt, this.position)));

            let cps = [];
            for(let i = 1; i <= this.size; i++) {
                cps = convert(circlePoints(i * this.scale, Math.PI / 2, 0, null, true));
                paths.push(cps);
            }
            
            let bcps = [];
            for(let i = 1; i < this.size; i++) {
                bcps = convert(circlePoints(i * this.scale, Math.PI / 2, Math.PI, null, true), -1);
                const intersections = pathsIntersections(cps, bcps);
                if(intersections.length == 0) {
                    paths.push(bcps);
                } else {
                    const filtered = bcps.filter((v, i) => i < intersections[1][2]);
                    paths.push((filtered.length == 0? [bcps[0]]:filtered).concat([intersections[1][0]]));
                    paths.push([intersections[0][0]].concat(bcps.filter((v, i) => i > intersections[0][2])));
                }
            }

            return paths.map(path => path.map(pt => trans2(rot, pt)));
        }
    }
    
    return new Tile(position, size, scale);
}

function Grid(drawSize, size) {
    class Grid {
        constructor(drawSize, size) {
            this.tiles = [];
            this.drawSize = drawSize;
            this.grid = Array.from({length: size}).map((v, c) => Array.from({length: size}).map((v, r) => [c, r]));
            this.cellDrawSize = this.drawSize / size;
        }
        canFit(column, row, size) {
            for(let c = column; c < column + size; c++) {
                for(let r = row; r < row + size; r++) {
                    if(c >= this.grid.length || r >= this.grid.length || this.grid[c][r] === true) {
                        return false;
                    }
                }
            }
            return true;
        }
        addTile(column, row, size) {
            for(let c = column; c < column + size; c++) {
                for(let r = row; r < row + size; r++) {
                    this.grid[c][r] = true;
                }
            }
            this.tiles.push(
                new Tile([
                    (column + size/2) * this.cellDrawSize - this.drawSize / 2,
                    (row + size/2) * this.cellDrawSize - this.drawSize / 2
                ], size, this.cellDrawSize)
            );
            this.tiles[this.tiles.length - 1].draw(turtle);
        }
        getFreeCells() {
            return this.grid.flatMap(c => c.filter(r => r !== true));
        }
    }
    return new Grid(drawSize, size);
}

/// Below is the standard lib I just copy paste under almost all my turtles

function approx1(a,b,delta=0.0001) { return -delta < a-b && a-b < delta }

////////////////////////////////////////////////////////////////
// 2D Vector Math utility code - Created by several Turtletoy users
////////////////////////////////////////////////////////////////
function norm2(a) { return scale2(a, 1/len2(a)); }
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 mul2(a, b) { return [a[0]*b[0], a[1]*b[1]]; }
function scale2(a, s) { return [a[0]*s,a[1]*s]; }
function lerp2(a,b,t) { return [a[0]*(1-t) + b[0]*t, a[1]*(1-t) + b[1]*t]; }
function lenSq2(a) { return a[0]**2+a[1]**2; }
function len2(a) { return Math.sqrt(lenSq2(a)); }
function rot2(a) { return [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a)]; }
function trans2(m, a) { return [m[0]*a[0]+m[2]*a[1], m[1]*a[0]+m[3]*a[1]]; } //Matrix(2x1) x Matrix(2x2)
function dist2(a,b) { return Math.hypot(...sub2(a,b)); }
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 multiply2(a2x2, a) { return [(a[0]*a2x2[0])+(a[1]*a2x2[1]),(a[0]*a2x2[2])+(a[1]*a2x2[3])]; } //Matrix(2x2) x Matrix(1x2)
function intersect_info2(as, ad, bs, bd) {
    const d = [bs[0] - as[0], bs[1] - as[1]];
    const det = bd[0] * ad[1] - bd[1] * ad[0];
    if(det === 0) return false;
    const res = [(d[1] * bd[0] - d[0] * bd[1]) / det, (d[1] * ad[0] - d[0] * ad[1]) / det];
    return [...res, add2(as, scale2(ad, res[0]))];
}
function intersect_ray2(a, b, c, d) {
    const i = intersect_info2(a, b, c, d);
    return i === false? i: i[2];
}
function segment_intersect2(a,b,c,d, inclusive = true) {
    const i = intersect_info2(a, sub2(b, a), c, sub2(d, c));
    if(i === false) return false;
    const t = inclusive? 0<=i[0]&&i[0]<=1&&0<=i[1]&&i[1]<=1: 0<i[0]&&i[0]<1&&0<i[1]&&i[1]<1;
    return t?i[2]:false;
}
function approx2(a,b,delta=0.0001) { return len2(sub2(a,b)) < delta }
function eq2(a,b) { return a[0]==b[0]&&a[1]==b[1]; }
function clamp2(a, tl, br) { return [Math.max(Math.min(br[0], a[0]), tl[0]), Math.max(Math.min(br[1], a[1]), tl[1])]; }
function nearSq2(test, near, delta = .0001) {
    return near[0] - delta < test[0] && test[0] < near[0] + delta &&
           near[1] - delta < test[1] && test[1] < near[1] + delta;
}

////////////////////////////////////////////////////////////////
// Start of some path utility code - Created by Jurgen Westerhof 2023
////////////////////////////////////////////////////////////////
function circlePoints(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(); }
function pts2Edges(pts) { return pts.map((v, i, a) => [v, a[(i+1)%a.length]]); }
function drawPath(turtle, pts) { return pts.forEach((pt, i) => turtle[i == 0? 'jump':'goto'](pt)); }
function drawTour(turtle, pts) { return drawPath(turtle, pts.concat([pts[0]])); }
function drawPoint(turtle, pt) { return drawTour(turtle, circlePoints(.5).map(p => add2(p, pt))); }
function isInPolygon(edges, pt) { return edges.map(edge => intersect_info2(edge[0], sub2(edge[1], edge[0]), pt, [0, 300])).filter(ii => ii !== false && 0 <= ii[0] && ii[0] <= 1 && 0 < ii[1]).length % 2 == 1; }
function isInVectorTour(vectors, pt) { return vectors.map(v => intersect_info2(...v, pt[0], pt[1])).filter(ii => ii !== false && 0 <= ii[0] && ii[0] < 1 && 0 <= ii[1]).length % 2 == 1; }
function tourToVectors(path) { return path.map((v, i, a) => [v, sub2(a[(i+1)%a.length], v)]); }
function thickLinePaths(from, to, thickness) { return [trans2(rot2(Math.atan2(...sub2(to, from))), [thickness/2, 0])].map(v => [[add2(from, v), add2(to, v)], [sub2(from, v), sub2(to, v)]]).pop();}
function toursIntersect(path1, path2) { return path1.some((pt1, i1) => path2.some((pt2, i2) => segment_intersect2(pt1, path1[(i1 + 1) % path1.length], pt2, path2[(i2 + 1) % path2.length]) !== false)); }

function pathsIntersect(path1, path2) { return path1.some((pt1, i1) => 
        i1 == path1.length - 1? false: path2.some((pt2, i2) => 
            i2 == path2.length - 1? false: 
            segment_intersect2(pt1, path1[i1 + 1], pt2, path2[i2 + 1]) !== false
        )
    );
}
function pathsIntersections(path1, path2) { return path1.flatMap((pt1, i1) => 
        i1 == path1.length - 1? [[false]]: path2.map((pt2, i2) => 
            i2 == path2.length - 1? [false]: 
            [segment_intersect2(pt1, path1[i1 + 1], pt2, path2[i2 + 1]), i1, i2]
        )
    ).filter(v => v[0] !== false);
}





// Fisher-Yates (aka Knuth) Shuffle
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array#2450976
function shuffle(array) {
  let currentIndex = array.length,  randomIndex;

  // While there remain elements to shuffle.
  while (currentIndex > 0) {

    // Pick a remaining element.
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex--;

    // And swap it with the current element.
    [array[currentIndex], array[randomIndex]] = [
      array[randomIndex], array[currentIndex]];
  }

  return array;
}